Marco Fugaro 4 rokov pred
rodič
commit
d9a5084d26
100 zmenil súbory, kde vykonal 33233 pridanie a 28782 odobranie
  1. 56 61
      examples/js/WebGL.js
  2. 64 73
      examples/js/animation/AnimationClipCreator.js
  3. 241 292
      examples/js/animation/CCDIKSolver.js
  4. 546 676
      examples/js/animation/MMDAnimationHelper.js
  5. 656 813
      examples/js/animation/MMDPhysics.js
  6. 124 160
      examples/js/cameras/CinematicCamera.js
  7. 84 89
      examples/js/controls/DeviceOrientationControls.js
  8. 209 174
      examples/js/controls/DragControls.js
  9. 229 220
      examples/js/controls/FirstPersonControls.js
  10. 255 185
      examples/js/controls/FlyControls.js
  11. 642 823
      examples/js/controls/OrbitControls.js
  12. 95 100
      examples/js/controls/PointerLockControls.js
  13. 446 469
      examples/js/controls/TrackballControls.js
  14. 928 1176
      examples/js/controls/TransformControls.js
  15. 1044 0
      examples/js/controls/experimental/CameraControls.js
  16. 382 0
      examples/js/csm/CSM.js
  17. 145 0
      examples/js/csm/CSMHelper.js
  18. 241 0
      examples/js/csm/CSMShader.js
  19. 133 0
      examples/js/csm/Frustum.js
  20. 229 291
      examples/js/curves/CurveExtras.js
  21. 47 39
      examples/js/curves/NURBSCurve.js
  22. 33 26
      examples/js/curves/NURBSSurface.js
  23. 258 285
      examples/js/curves/NURBSUtils.js
  24. 1625 0
      examples/js/deprecated/Geometry.js
  25. 67 123
      examples/js/effects/AnaglyphEffect.js
  26. 180 184
      examples/js/effects/AsciiEffect.js
  27. 258 349
      examples/js/effects/OutlineEffect.js
  28. 47 73
      examples/js/effects/ParallaxBarrierEffect.js
  29. 106 86
      examples/js/effects/PeppersGhostEffect.js
  30. 29 27
      examples/js/effects/StereoEffect.js
  31. 53 0
      examples/js/environments/DebugEnvironment.js
  32. 100 0
      examples/js/environments/RoomEnvironment.js
  33. 283 477
      examples/js/exporters/ColladaExporter.js
  34. 126 144
      examples/js/exporters/DRACOExporter.js
  35. 902 1026
      examples/js/exporters/GLTFExporter.js
  36. 112 126
      examples/js/exporters/MMDExporter.js
  37. 154 174
      examples/js/exporters/OBJExporter.js
  38. 271 354
      examples/js/exporters/PLYExporter.js
  39. 124 128
      examples/js/exporters/STLExporter.js
  40. 386 0
      examples/js/exporters/USDZExporter.js
  41. 46 47
      examples/js/geometries/BoxLineGeometry.js
  42. 27 32
      examples/js/geometries/ConvexGeometry.js
  43. 179 234
      examples/js/geometries/DecalGeometry.js
  44. 563 693
      examples/js/geometries/LightningStrike.js
  45. 163 194
      examples/js/geometries/ParametricGeometries.js
  46. 137 0
      examples/js/geometries/RoundedBoxGeometry.js
  47. 36 474
      examples/js/geometries/TeapotGeometry.js
  48. 49 0
      examples/js/helpers/LightProbeHelper.js
  49. 89 0
      examples/js/helpers/PositionalAudioHelper.js
  50. 73 0
      examples/js/helpers/RectAreaLightHelper.js
  51. 91 0
      examples/js/helpers/VertexNormalsHelper.js
  52. 72 0
      examples/js/helpers/VertexTangentsHelper.js
  53. 141 157
      examples/js/interactive/SelectionBox.js
  54. 50 53
      examples/js/interactive/SelectionHelper.js
  55. 150 158
      examples/js/lights/LightProbeGenerator.js
  56. 11 16
      examples/js/lights/RectAreaLightUniformsLib.js
  57. 15 12
      examples/js/lines/Line2.js
  58. 54 64
      examples/js/lines/LineGeometry.js
  59. 138 183
      examples/js/lines/LineMaterial.js
  60. 169 206
      examples/js/lines/LineSegments2.js
  61. 130 159
      examples/js/lines/LineSegmentsGeometry.js
  62. 36 35
      examples/js/lines/Wireframe.js
  63. 12 13
      examples/js/lines/WireframeGeometry2.js
  64. 1264 0
      examples/js/loaders/3DMLoader.js
  65. 797 909
      examples/js/loaders/3MFLoader.js
  66. 290 268
      examples/js/loaders/AMFLoader.js
  67. 225 258
      examples/js/loaders/BVHLoader.js
  68. 458 499
      examples/js/loaders/BasisTextureLoader.js
  69. 2277 2502
      examples/js/loaders/ColladaLoader.js
  70. 187 208
      examples/js/loaders/DDSLoader.js
  71. 383 405
      examples/js/loaders/DRACOLoader.js
  72. 430 474
      examples/js/loaders/EXRLoader.js
  73. 2385 2771
      examples/js/loaders/FBXLoader.js
  74. 156 153
      examples/js/loaders/GCodeLoader.js
  75. 2094 2412
      examples/js/loaders/GLTFLoader.js
  76. 60 75
      examples/js/loaders/HDRCubeTextureLoader.js
  77. 67 65
      examples/js/loaders/KMZLoader.js
  78. 127 119
      examples/js/loaders/KTXLoader.js
  79. 1119 1261
      examples/js/loaders/LDrawLoader.js
  80. 130 0
      examples/js/loaders/LUT3dlLoader.js
  81. 139 0
      examples/js/loaders/LUTCubeLoader.js
  82. 958 0
      examples/js/loaders/LWOLoader.js
  83. 62 0
      examples/js/loaders/LottieLoader.js
  84. 26 170
      examples/js/loaders/MD2Loader.js
  85. 93 0
      examples/js/loaders/MDDLoader.js
  86. 115 171
      examples/js/loaders/MMDLoader.js
  87. 282 351
      examples/js/loaders/MTLLoader.js
  88. 408 407
      examples/js/loaders/NRRDLoader.js
  89. 380 480
      examples/js/loaders/OBJLoader.js
  90. 213 251
      examples/js/loaders/PCDLoader.js
  91. 99 123
      examples/js/loaders/PDBLoader.js
  92. 309 301
      examples/js/loaders/PLYLoader.js
  93. 167 198
      examples/js/loaders/PRWMLoader.js
  94. 177 193
      examples/js/loaders/PVRLoader.js
  95. 330 309
      examples/js/loaders/RGBELoader.js
  96. 1369 0
      examples/js/loaders/RGBMLoader.js
  97. 218 238
      examples/js/loaders/STLLoader.js
  98. 359 450
      examples/js/loaders/SVGLoader.js
  99. 424 466
      examples/js/loaders/TDSLoader.js
  100. 315 342
      examples/js/loaders/TGALoader.js

+ 56 - 61
examples/js/WebGL.js

@@ -1,89 +1,84 @@
-THREE.WEBGL = {
+( function () {
 
-	isWebGLAvailable: function () {
+	var WEBGL = {
+		isWebGLAvailable: function () {
 
-		try {
+			try {
 
-			var canvas = document.createElement( 'canvas' );
-			return !! ( window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ) );
+				var canvas = document.createElement( 'canvas' );
+				return !! ( window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ) );
 
-		} catch ( e ) {
+			} catch ( e ) {
 
-			return false;
+				return false;
 
-		}
-
-	},
-
-	isWebGL2Available: function () {
-
-		try {
-
-			var canvas = document.createElement( 'canvas' );
-			return !! ( window.WebGL2RenderingContext && canvas.getContext( 'webgl2' ) );
+			}
 
-		} catch ( e ) {
+		},
+		isWebGL2Available: function () {
 
-			return false;
+			try {
 
-		}
-
-	},
+				var canvas = document.createElement( 'canvas' );
+				return !! ( window.WebGL2RenderingContext && canvas.getContext( 'webgl2' ) );
 
-	getWebGLErrorMessage: function () {
+			} catch ( e ) {
 
-		return this.getErrorMessage( 1 );
+				return false;
 
-	},
+			}
 
-	getWebGL2ErrorMessage: function () {
+		},
+		getWebGLErrorMessage: function () {
 
-		return this.getErrorMessage( 2 );
+			return this.getErrorMessage( 1 );
 
-	},
+		},
+		getWebGL2ErrorMessage: function () {
 
-	getErrorMessage: function ( version ) {
+			return this.getErrorMessage( 2 );
 
-		var names = {
-			1: 'WebGL',
-			2: 'WebGL 2'
-		};
+		},
+		getErrorMessage: function ( version ) {
 
-		var contexts = {
-			1: window.WebGLRenderingContext,
-			2: window.WebGL2RenderingContext
-		};
+			var names = {
+				1: 'WebGL',
+				2: 'WebGL 2'
+			};
+			var contexts = {
+				1: window.WebGLRenderingContext,
+				2: window.WebGL2RenderingContext
+			};
+			var message = 'Your $0 does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation" style="color:#000">$1</a>';
+			var element = document.createElement( 'div' );
+			element.id = 'webglmessage';
+			element.style.fontFamily = 'monospace';
+			element.style.fontSize = '13px';
+			element.style.fontWeight = 'normal';
+			element.style.textAlign = 'center';
+			element.style.background = '#fff';
+			element.style.color = '#000';
+			element.style.padding = '1.5em';
+			element.style.width = '400px';
+			element.style.margin = '5em auto 0';
 
-		var message = 'Your $0 does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation" style="color:#000">$1</a>';
+			if ( contexts[ version ] ) {
 
-		var element = document.createElement( 'div' );
-		element.id = 'webglmessage';
-		element.style.fontFamily = 'monospace';
-		element.style.fontSize = '13px';
-		element.style.fontWeight = 'normal';
-		element.style.textAlign = 'center';
-		element.style.background = '#fff';
-		element.style.color = '#000';
-		element.style.padding = '1.5em';
-		element.style.width = '400px';
-		element.style.margin = '5em auto 0';
+				message = message.replace( '$0', 'graphics card' );
 
-		if ( contexts[ version ] ) {
+			} else {
 
-			message = message.replace( '$0', 'graphics card' );
+				message = message.replace( '$0', 'browser' );
 
-		} else {
+			}
 
-			message = message.replace( '$0', 'browser' );
+			message = message.replace( '$1', names[ version ] );
+			element.innerHTML = message;
+			return element;
 
 		}
+	};
 
-		message = message.replace( '$1', names[ version ] );
-
-		element.innerHTML = message;
-
-		return element;
-
-	}
+	THREE.WEBGL = WEBGL;
 
-};
+} )();

+ 64 - 73
examples/js/animation/AnimationClipCreator.js

@@ -1,106 +1,97 @@
-THREE.AnimationClipCreator = function () {};
+( function () {
 
-THREE.AnimationClipCreator.CreateRotationAnimation = function ( period, axis ) {
+	var AnimationClipCreator = function () {};
 
-	var times = [ 0, period ], values = [ 0, 360 ];
+	AnimationClipCreator.CreateRotationAnimation = function ( period, axis ) {
 
-	axis = axis || 'x';
-	var trackName = '.rotation[' + axis + ']';
+		var times = [ 0, period ],
+			values = [ 0, 360 ];
+		axis = axis || 'x';
+		var trackName = '.rotation[' + axis + ']';
+		var track = new THREE.NumberKeyframeTrack( trackName, times, values );
+		return new THREE.AnimationClip( null, period, [ track ] );
 
-	var track = new THREE.NumberKeyframeTrack( trackName, times, values );
+	};
 
-	return new THREE.AnimationClip( null, period, [ track ] );
+	AnimationClipCreator.CreateScaleAxisAnimation = function ( period, axis ) {
 
-};
+		var times = [ 0, period ],
+			values = [ 0, 1 ];
+		axis = axis || 'x';
+		var trackName = '.scale[' + axis + ']';
+		var track = new THREE.NumberKeyframeTrack( trackName, times, values );
+		return new THREE.AnimationClip( null, period, [ track ] );
 
-THREE.AnimationClipCreator.CreateScaleAxisAnimation = function ( period, axis ) {
+	};
 
-	var times = [ 0, period ], values = [ 0, 1 ];
+	AnimationClipCreator.CreateShakeAnimation = function ( duration, shakeScale ) {
 
-	axis = axis || 'x';
-	var trackName = '.scale[' + axis + ']';
+		var times = [],
+			values = [],
+			tmp = new THREE.Vector3();
 
-	var track = new THREE.NumberKeyframeTrack( trackName, times, values );
+		for ( var i = 0; i < duration * 10; i ++ ) {
 
-	return new THREE.AnimationClip( null, period, [ track ] );
+			times.push( i / 10 );
+			tmp.set( Math.random() * 2.0 - 1.0, Math.random() * 2.0 - 1.0, Math.random() * 2.0 - 1.0 ).multiply( shakeScale ).toArray( values, values.length );
 
-};
+		}
 
-THREE.AnimationClipCreator.CreateShakeAnimation = function ( duration, shakeScale ) {
+		var trackName = '.position';
+		var track = new THREE.VectorKeyframeTrack( trackName, times, values );
+		return new THREE.AnimationClip( null, duration, [ track ] );
 
-	var times = [], values = [], tmp = new THREE.Vector3();
+	};
 
-	for ( var i = 0; i < duration * 10; i ++ ) {
+	AnimationClipCreator.CreatePulsationAnimation = function ( duration, pulseScale ) {
 
-		times.push( i / 10 );
+		var times = [],
+			values = [],
+			tmp = new THREE.Vector3();
 
-		tmp.set( Math.random() * 2.0 - 1.0, Math.random() * 2.0 - 1.0, Math.random() * 2.0 - 1.0 ).
-			multiply( shakeScale ).
-			toArray( values, values.length );
+		for ( var i = 0; i < duration * 10; i ++ ) {
 
-	}
+			times.push( i / 10 );
+			var scaleFactor = Math.random() * pulseScale;
+			tmp.set( scaleFactor, scaleFactor, scaleFactor ).toArray( values, values.length );
 
-	var trackName = '.position';
+		}
 
-	var track = new THREE.VectorKeyframeTrack( trackName, times, values );
+		var trackName = '.scale';
+		var track = new THREE.VectorKeyframeTrack( trackName, times, values );
+		return new THREE.AnimationClip( null, duration, [ track ] );
 
-	return new THREE.AnimationClip( null, duration, [ track ] );
+	};
 
-};
+	AnimationClipCreator.CreateVisibilityAnimation = function ( duration ) {
 
+		var times = [ 0, duration / 2, duration ],
+			values = [ true, false, true ];
+		var trackName = '.visible';
+		var track = new THREE.BooleanKeyframeTrack( trackName, times, values );
+		return new THREE.AnimationClip( null, duration, [ track ] );
 
-THREE.AnimationClipCreator.CreatePulsationAnimation = function ( duration, pulseScale ) {
+	};
 
-	var times = [], values = [], tmp = new THREE.Vector3();
+	AnimationClipCreator.CreateMaterialColorAnimation = function ( duration, colors ) {
 
-	for ( var i = 0; i < duration * 10; i ++ ) {
+		var times = [],
+			values = [],
+			timeStep = duration / colors.length;
 
-		times.push( i / 10 );
+		for ( var i = 0; i <= colors.length; i ++ ) {
 
-		var scaleFactor = Math.random() * pulseScale;
-		tmp.set( scaleFactor, scaleFactor, scaleFactor ).
-			toArray( values, values.length );
+			times.push( i * timeStep );
+			values.push( colors[ i % colors.length ] );
 
-	}
+		}
 
-	var trackName = '.scale';
+		var trackName = '.material[0].color';
+		var track = new THREE.ColorKeyframeTrack( trackName, times, values );
+		return new THREE.AnimationClip( null, duration, [ track ] );
 
-	var track = new THREE.VectorKeyframeTrack( trackName, times, values );
+	};
 
-	return new THREE.AnimationClip( null, duration, [ track ] );
+	THREE.AnimationClipCreator = AnimationClipCreator;
 
-};
-
-
-THREE.AnimationClipCreator.CreateVisibilityAnimation = function ( duration ) {
-
-	var times = [ 0, duration / 2, duration ], values = [ true, false, true ];
-
-	var trackName = '.visible';
-
-	var track = new THREE.BooleanKeyframeTrack( trackName, times, values );
-
-	return new THREE.AnimationClip( null, duration, [ track ] );
-
-};
-
-
-THREE.AnimationClipCreator.CreateMaterialColorAnimation = function ( duration, colors ) {
-
-	var times = [], values = [],
-		timeStep = duration / colors.length;
-
-	for ( var i = 0; i <= colors.length; i ++ ) {
-
-		times.push( i * timeStep );
-		values.push( colors[ i % colors.length ] );
-
-	}
-
-	var trackName = '.material[0].color';
-
-	var track = new THREE.ColorKeyframeTrack( trackName, times, values );
-
-	return new THREE.AnimationClip( null, duration, [ track ] );
-
-};
+} )();

+ 241 - 292
examples/js/animation/CCDIKSolver.js

@@ -1,12 +1,14 @@
-/**
+( function () {
+
+	/**
  * CCD Algorithm
- *  - https://sites.google.com/site/auraliusproject/ccd-algorithm
+ *	- https://sites.google.com/site/auraliusproject/ccd-algorithm
  *
  * // ik parameter example
  * //
  * // target, effector, index in links are bone index in skeleton.bones.
  * // the bones relation should be
- * // <-- parent                                  child -->
+ * // <-- parent																	child -->
  * // links[ n ], links[ n - 1 ], ..., links[ 0 ], effector
  * iks = [ {
  *	target: 1,
@@ -18,450 +20,397 @@
  * } ];
  */
 
-THREE.CCDIKSolver = ( function () {
+	var CCDIKSolver = function () {
 
-	/**
+		/**
 	 * @param {THREE.SkinnedMesh} mesh
 	 * @param {Array<Object>} iks
 	 */
-	function CCDIKSolver( mesh, iks ) {
-
-		this.mesh = mesh;
-		this.iks = iks || [];
+		function CCDIKSolver( mesh, iks ) {
 
-		this._valid();
+			this.mesh = mesh;
+			this.iks = iks || [];
 
-	}
+			this._valid();
 
-	CCDIKSolver.prototype = {
+		}
 
-		constructor: CCDIKSolver,
+		CCDIKSolver.prototype = {
+			constructor: CCDIKSolver,
 
-		/**
+			/**
 		 * Update all IK bones.
 		 *
 		 * @return {CCDIKSolver}
 		 */
-		update: function () {
+			update: function () {
 
-			var iks = this.iks;
+				var iks = this.iks;
 
-			for ( var i = 0, il = iks.length; i < il; i ++ ) {
+				for ( var i = 0, il = iks.length; i < il; i ++ ) {
 
-				this.updateOne( iks[ i ] );
+					this.updateOne( iks[ i ] );
 
-			}
+				}
 
-			return this;
+				return this;
 
-		},
+			},
 
-		/**
+			/**
 		 * Update one IK bone
 		 *
 		 * @param {Object} ik parameter
-		 * @return {THREE.CCDIKSolver}
+		 * @return {CCDIKSolver}
 		 */
-		updateOne: function () {
+			updateOne: function () {
 
-			var q = new THREE.Quaternion();
-			var targetPos = new THREE.Vector3();
-			var targetVec = new THREE.Vector3();
-			var effectorPos = new THREE.Vector3();
-			var effectorVec = new THREE.Vector3();
-			var linkPos = new THREE.Vector3();
-			var invLinkQ = new THREE.Quaternion();
-			var linkScale = new THREE.Vector3();
-			var axis = new THREE.Vector3();
-			var vector = new THREE.Vector3();
+				var q = new THREE.Quaternion();
+				var targetPos = new THREE.Vector3();
+				var targetVec = new THREE.Vector3();
+				var effectorPos = new THREE.Vector3();
+				var effectorVec = new THREE.Vector3();
+				var linkPos = new THREE.Vector3();
+				var invLinkQ = new THREE.Quaternion();
+				var linkScale = new THREE.Vector3();
+				var axis = new THREE.Vector3();
+				var vector = new THREE.Vector3();
+				return function update( ik ) {
 
-			return function update( ik ) {
+					var bones = this.mesh.skeleton.bones; // for reference overhead reduction in loop
 
-				var bones = this.mesh.skeleton.bones;
-
-				// for reference overhead reduction in loop
-				var math = Math;
-
-				var effector = bones[ ik.effector ];
-				var target = bones[ ik.target ];
+					var math = Math;
+					var effector = bones[ ik.effector ];
+					var target = bones[ ik.target ]; // don't use getWorldPosition() here for the performance
+					// because it calls updateMatrixWorld( true ) inside.
 
-				// don't use getWorldPosition() here for the performance
-				// because it calls updateMatrixWorld( true ) inside.
-				targetPos.setFromMatrixPosition( target.matrixWorld );
+					targetPos.setFromMatrixPosition( target.matrixWorld );
+					var links = ik.links;
+					var iteration = ik.iteration !== undefined ? ik.iteration : 1;
 
-				var links = ik.links;
-				var iteration = ik.iteration !== undefined ? ik.iteration : 1;
+					for ( var i = 0; i < iteration; i ++ ) {
 
-				for ( var i = 0; i < iteration; i ++ ) {
+						var rotated = false;
 
-					var rotated = false;
+						for ( var j = 0, jl = links.length; j < jl; j ++ ) {
 
-					for ( var j = 0, jl = links.length; j < jl; j ++ ) {
-
-						var link = bones[ links[ j ].index ];
+							var link = bones[ links[ j ].index ]; // skip this link and following links.
+							// this skip is used for MMD performance optimization.
 
-						// skip this link and following links.
-						// this skip is used for MMD performance optimization.
-						if ( links[ j ].enabled === false ) break;
+							if ( links[ j ].enabled === false ) break;
+							var limitation = links[ j ].limitation;
+							var rotationMin = links[ j ].rotationMin;
+							var rotationMax = links[ j ].rotationMax; // don't use getWorldPosition/Quaternion() here for the performance
+							// because they call updateMatrixWorld( true ) inside.
 
-						var limitation = links[ j ].limitation;
-						var rotationMin = links[ j ].rotationMin;
-						var rotationMax = links[ j ].rotationMax;
+							link.matrixWorld.decompose( linkPos, invLinkQ, linkScale );
+							invLinkQ.invert();
+							effectorPos.setFromMatrixPosition( effector.matrixWorld ); // work in link world
 
-						// don't use getWorldPosition/Quaternion() here for the performance
-						// because they call updateMatrixWorld( true ) inside.
-						link.matrixWorld.decompose( linkPos, invLinkQ, linkScale );
-						invLinkQ.invert();
-						effectorPos.setFromMatrixPosition( effector.matrixWorld );
+							effectorVec.subVectors( effectorPos, linkPos );
+							effectorVec.applyQuaternion( invLinkQ );
+							effectorVec.normalize();
+							targetVec.subVectors( targetPos, linkPos );
+							targetVec.applyQuaternion( invLinkQ );
+							targetVec.normalize();
+							var angle = targetVec.dot( effectorVec );
 
-						// work in link world
-						effectorVec.subVectors( effectorPos, linkPos );
-						effectorVec.applyQuaternion( invLinkQ );
-						effectorVec.normalize();
+							if ( angle > 1.0 ) {
 
-						targetVec.subVectors( targetPos, linkPos );
-						targetVec.applyQuaternion( invLinkQ );
-						targetVec.normalize();
+								angle = 1.0;
 
-						var angle = targetVec.dot( effectorVec );
+							} else if ( angle < - 1.0 ) {
 
-						if ( angle > 1.0 ) {
+								angle = - 1.0;
 
-							angle = 1.0;
+							}
 
-						} else if ( angle < - 1.0 ) {
+							angle = math.acos( angle ); // skip if changing angle is too small to prevent vibration of bone
+							// Refer to http://www20.atpages.jp/katwat/three.js_r58/examples/mytest37/mmd.three.js
 
-							angle = - 1.0;
+							if ( angle < 1e-5 ) continue;
 
-						}
+							if ( ik.minAngle !== undefined && angle < ik.minAngle ) {
 
-						angle = math.acos( angle );
-
-						// skip if changing angle is too small to prevent vibration of bone
-						// Refer to http://www20.atpages.jp/katwat/three.js_r58/examples/mytest37/mmd.three.js
-						if ( angle < 1e-5 ) continue;
-
-						if ( ik.minAngle !== undefined && angle < ik.minAngle ) {
-
-							angle = ik.minAngle;
-
-						}
+								angle = ik.minAngle;
 
-						if ( ik.maxAngle !== undefined && angle > ik.maxAngle ) {
+							}
 
-							angle = ik.maxAngle;
+							if ( ik.maxAngle !== undefined && angle > ik.maxAngle ) {
 
-						}
+								angle = ik.maxAngle;
 
-						axis.crossVectors( effectorVec, targetVec );
-						axis.normalize();
+							}
 
-						q.setFromAxisAngle( axis, angle );
-						link.quaternion.multiply( q );
+							axis.crossVectors( effectorVec, targetVec );
+							axis.normalize();
+							q.setFromAxisAngle( axis, angle );
+							link.quaternion.multiply( q ); // TODO: re-consider the limitation specification
 
-						// TODO: re-consider the limitation specification
-						if ( limitation !== undefined ) {
+							if ( limitation !== undefined ) {
 
-							var c = link.quaternion.w;
+								var c = link.quaternion.w;
+								if ( c > 1.0 ) c = 1.0;
+								var c2 = math.sqrt( 1 - c * c );
+								link.quaternion.set( limitation.x * c2, limitation.y * c2, limitation.z * c2, c );
 
-							if ( c > 1.0 ) c = 1.0;
+							}
 
-							var c2 = math.sqrt( 1 - c * c );
-							link.quaternion.set( limitation.x * c2,
-							                     limitation.y * c2,
-							                     limitation.z * c2,
-							                     c );
+							if ( rotationMin !== undefined ) {
 
-						}
+								link.rotation.setFromVector3( link.rotation.toVector3( vector ).max( rotationMin ) );
 
-						if ( rotationMin !== undefined ) {
+							}
 
-							link.rotation.setFromVector3(
-								link.rotation
-									.toVector3( vector )
-									.max( rotationMin ) );
+							if ( rotationMax !== undefined ) {
 
-						}
+								link.rotation.setFromVector3( link.rotation.toVector3( vector ).min( rotationMax ) );
 
-						if ( rotationMax !== undefined ) {
+							}
 
-							link.rotation.setFromVector3(
-								link.rotation
-									.toVector3( vector )
-									.min( rotationMax ) );
+							link.updateMatrixWorld( true );
+							rotated = true;
 
 						}
 
-						link.updateMatrixWorld( true );
-
-						rotated = true;
+						if ( ! rotated ) break;
 
 					}
 
-					if ( ! rotated ) break;
-
-				}
-
-				return this;
+					return this;
 
-			};
+				};
 
-		}(),
+			}(),
 
-		/**
+			/**
 		 * Creates Helper
 		 *
 		 * @return {CCDIKHelper}
 		 */
-		createHelper: function () {
+			createHelper: function () {
 
-			return new CCDIKHelper( this.mesh, this.mesh.geometry.userData.MMD.iks );
+				return new CCDIKHelper( this.mesh, this.mesh.geometry.userData.MMD.iks );
 
-		},
+			},
+			// private methods
+			_valid: function () {
 
-		// private methods
-
-		_valid: function () {
+				var iks = this.iks;
+				var bones = this.mesh.skeleton.bones;
 
-			var iks = this.iks;
-			var bones = this.mesh.skeleton.bones;
+				for ( var i = 0, il = iks.length; i < il; i ++ ) {
 
-			for ( var i = 0, il = iks.length; i < il; i ++ ) {
+					var ik = iks[ i ];
+					var effector = bones[ ik.effector ];
+					var links = ik.links;
+					var link0, link1;
+					link0 = effector;
 
-				var ik = iks[ i ];
-				var effector = bones[ ik.effector ];
-				var links = ik.links;
-				var link0, link1;
+					for ( var j = 0, jl = links.length; j < jl; j ++ ) {
 
-				link0 = effector;
+						link1 = bones[ links[ j ].index ];
 
-				for ( var j = 0, jl = links.length; j < jl; j ++ ) {
+						if ( link0.parent !== link1 ) {
 
-					link1 = bones[ links[ j ].index ];
+							console.warn( 'THREE.CCDIKSolver: bone ' + link0.name + ' is not the child of bone ' + link1.name );
 
-					if ( link0.parent !== link1 ) {
+						}
 
-						console.warn( 'THREE.CCDIKSolver: bone ' + link0.name + ' is not the child of bone ' + link1.name );
+						link0 = link1;
 
 					}
 
-					link0 = link1;
-
 				}
 
 			}
-
-		}
-
-	};
-
-	/**
+		};
+		/**
 	 * Visualize IK bones
 	 *
 	 * @param {SkinnedMesh} mesh
 	 * @param {Array<Object>} iks
 	 */
-	function CCDIKHelper( mesh, iks ) {
-
-		THREE.Object3D.call( this );
-
-		this.root = mesh;
-		this.iks = iks || [];
-
-		this.matrix.copy( mesh.matrixWorld );
-		this.matrixAutoUpdate = false;
 
-		this.sphereGeometry = new THREE.SphereGeometry( 0.25, 16, 8 );
+		function CCDIKHelper( mesh, iks ) {
+
+			THREE.Object3D.call( this );
+			this.root = mesh;
+			this.iks = iks || [];
+			this.matrix.copy( mesh.matrixWorld );
+			this.matrixAutoUpdate = false;
+			this.sphereGeometry = new THREE.SphereGeometry( 0.25, 16, 8 );
+			this.targetSphereMaterial = new THREE.MeshBasicMaterial( {
+				color: new THREE.Color( 0xff8888 ),
+				depthTest: false,
+				depthWrite: false,
+				transparent: true
+			} );
+			this.effectorSphereMaterial = new THREE.MeshBasicMaterial( {
+				color: new THREE.Color( 0x88ff88 ),
+				depthTest: false,
+				depthWrite: false,
+				transparent: true
+			} );
+			this.linkSphereMaterial = new THREE.MeshBasicMaterial( {
+				color: new THREE.Color( 0x8888ff ),
+				depthTest: false,
+				depthWrite: false,
+				transparent: true
+			} );
+			this.lineMaterial = new THREE.LineBasicMaterial( {
+				color: new THREE.Color( 0xff0000 ),
+				depthTest: false,
+				depthWrite: false,
+				transparent: true
+			} );
+
+			this._init();
 
-		this.targetSphereMaterial = new THREE.MeshBasicMaterial( {
-			color: new THREE.Color( 0xff8888 ),
-			depthTest: false,
-			depthWrite: false,
-			transparent: true
-		} );
-
-		this.effectorSphereMaterial = new THREE.MeshBasicMaterial( {
-			color: new THREE.Color( 0x88ff88 ),
-			depthTest: false,
-			depthWrite: false,
-			transparent: true
-		} );
-
-		this.linkSphereMaterial = new THREE.MeshBasicMaterial( {
-			color: new THREE.Color( 0x8888ff ),
-			depthTest: false,
-			depthWrite: false,
-			transparent: true
-		} );
-
-		this.lineMaterial = new THREE.LineBasicMaterial( {
-			color: new THREE.Color( 0xff0000 ),
-			depthTest: false,
-			depthWrite: false,
-			transparent: true
-		} );
-
-		this._init();
-
-	}
-
-	CCDIKHelper.prototype = Object.assign( Object.create( THREE.Object3D.prototype ), {
+		}
 
-		constructor: CCDIKHelper,
+		CCDIKHelper.prototype = Object.assign( Object.create( THREE.Object3D.prototype ), {
+			constructor: CCDIKHelper,
 
-		/**
+			/**
 		 * Updates IK bones visualization.
 		 */
-		updateMatrixWorld: function () {
+			updateMatrixWorld: function () {
 
-			var matrix = new THREE.Matrix4();
-			var vector = new THREE.Vector3();
+				var matrix = new THREE.Matrix4();
+				var vector = new THREE.Vector3();
 
-			function getPosition( bone, matrixWorldInv ) {
+				function getPosition( bone, matrixWorldInv ) {
 
-				return vector
-					.setFromMatrixPosition( bone.matrixWorld )
-					.applyMatrix4( matrixWorldInv );
+					return vector.setFromMatrixPosition( bone.matrixWorld ).applyMatrix4( matrixWorldInv );
 
-			}
-
-			function setPositionOfBoneToAttributeArray( array, index, bone, matrixWorldInv ) {
-
-				var v = getPosition( bone, matrixWorldInv );
+				}
 
-				array[ index * 3 + 0 ] = v.x;
-				array[ index * 3 + 1 ] = v.y;
-				array[ index * 3 + 2 ] = v.z;
+				function setPositionOfBoneToAttributeArray( array, index, bone, matrixWorldInv ) {
 
-			}
+					var v = getPosition( bone, matrixWorldInv );
+					array[ index * 3 + 0 ] = v.x;
+					array[ index * 3 + 1 ] = v.y;
+					array[ index * 3 + 2 ] = v.z;
 
-			return function updateMatrixWorld( force ) {
+				}
 
-				var mesh = this.root;
+				return function updateMatrixWorld( force ) {
 
-				if ( this.visible ) {
+					var mesh = this.root;
 
-					var offset = 0;
+					if ( this.visible ) {
 
-					var iks = this.iks;
-					var bones = mesh.skeleton.bones;
+						var offset = 0;
+						var iks = this.iks;
+						var bones = mesh.skeleton.bones;
+						matrix.copy( mesh.matrixWorld ).invert();
 
-					matrix.copy( mesh.matrixWorld ).invert();
+						for ( var i = 0, il = iks.length; i < il; i ++ ) {
 
-					for ( var i = 0, il = iks.length; i < il; i ++ ) {
+							var ik = iks[ i ];
+							var targetBone = bones[ ik.target ];
+							var effectorBone = bones[ ik.effector ];
+							var targetMesh = this.children[ offset ++ ];
+							var effectorMesh = this.children[ offset ++ ];
+							targetMesh.position.copy( getPosition( targetBone, matrix ) );
+							effectorMesh.position.copy( getPosition( effectorBone, matrix ) );
 
-						var ik = iks[ i ];
+							for ( var j = 0, jl = ik.links.length; j < jl; j ++ ) {
 
-						var targetBone = bones[ ik.target ];
-						var effectorBone = bones[ ik.effector ];
+								var link = ik.links[ j ];
+								var linkBone = bones[ link.index ];
+								var linkMesh = this.children[ offset ++ ];
+								linkMesh.position.copy( getPosition( linkBone, matrix ) );
 
-						var targetMesh = this.children[ offset ++ ];
-						var effectorMesh = this.children[ offset ++ ];
+							}
 
-						targetMesh.position.copy( getPosition( targetBone, matrix ) );
-						effectorMesh.position.copy( getPosition( effectorBone, matrix ) );
+							var line = this.children[ offset ++ ];
+							var array = line.geometry.attributes.position.array;
+							setPositionOfBoneToAttributeArray( array, 0, targetBone, matrix );
+							setPositionOfBoneToAttributeArray( array, 1, effectorBone, matrix );
 
-						for ( var j = 0, jl = ik.links.length; j < jl; j ++ ) {
+							for ( var j = 0, jl = ik.links.length; j < jl; j ++ ) {
 
-							var link = ik.links[ j ];
-							var linkBone = bones[ link.index ];
+								var link = ik.links[ j ];
+								var linkBone = bones[ link.index ];
+								setPositionOfBoneToAttributeArray( array, j + 2, linkBone, matrix );
 
-							var linkMesh = this.children[ offset ++ ];
+							}
 
-							linkMesh.position.copy( getPosition( linkBone, matrix ) );
+							line.geometry.attributes.position.needsUpdate = true;
 
 						}
 
-						var line = this.children[ offset ++ ];
-						var array = line.geometry.attributes.position.array;
-
-						setPositionOfBoneToAttributeArray( array, 0, targetBone, matrix );
-						setPositionOfBoneToAttributeArray( array, 1, effectorBone, matrix );
-
-						for ( var j = 0, jl = ik.links.length; j < jl; j ++ ) {
-
-							var link = ik.links[ j ];
-							var linkBone = bones[ link.index ];
-							setPositionOfBoneToAttributeArray( array, j + 2, linkBone, matrix );
-
-						}
-
-						line.geometry.attributes.position.needsUpdate = true;
-
 					}
 
-				}
-
-				this.matrix.copy( mesh.matrixWorld );
-
-				THREE.Object3D.prototype.updateMatrixWorld.call( this, force );
-
-			};
+					this.matrix.copy( mesh.matrixWorld );
+					THREE.Object3D.prototype.updateMatrixWorld.call( this, force );
 
-		}(),
+				};
 
-		// private method
+			}(),
+			// private method
+			_init: function () {
 
-		_init: function () {
+				var scope = this;
+				var iks = this.iks;
 
-			var scope = this;
-			var iks = this.iks;
+				function createLineGeometry( ik ) {
 
-			function createLineGeometry( ik ) {
+					var geometry = new THREE.BufferGeometry();
+					var vertices = new Float32Array( ( 2 + ik.links.length ) * 3 );
+					geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
+					return geometry;
 
-				var geometry = new THREE.BufferGeometry();
-				var vertices = new Float32Array( ( 2 + ik.links.length ) * 3 );
-				geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
-
-				return geometry;
+				}
 
-			}
+				function createTargetMesh() {
 
-			function createTargetMesh() {
+					return new THREE.Mesh( scope.sphereGeometry, scope.targetSphereMaterial );
 
-				return new THREE.Mesh( scope.sphereGeometry, scope.targetSphereMaterial );
+				}
 
-			}
+				function createEffectorMesh() {
 
-			function createEffectorMesh() {
+					return new THREE.Mesh( scope.sphereGeometry, scope.effectorSphereMaterial );
 
-				return new THREE.Mesh( scope.sphereGeometry, scope.effectorSphereMaterial );
+				}
 
-			}
+				function createLinkMesh() {
 
-			function createLinkMesh() {
+					return new THREE.Mesh( scope.sphereGeometry, scope.linkSphereMaterial );
 
-				return new THREE.Mesh( scope.sphereGeometry, scope.linkSphereMaterial );
+				}
 
-			}
+				function createLine( ik ) {
 
-			function createLine( ik ) {
+					return new THREE.Line( createLineGeometry( ik ), scope.lineMaterial );
 
-				return new THREE.Line( createLineGeometry( ik ), scope.lineMaterial );
+				}
 
-			}
+				for ( var i = 0, il = iks.length; i < il; i ++ ) {
 
-			for ( var i = 0, il = iks.length; i < il; i ++ ) {
+					var ik = iks[ i ];
+					this.add( createTargetMesh() );
+					this.add( createEffectorMesh() );
 
-				var ik = iks[ i ];
+					for ( var j = 0, jl = ik.links.length; j < jl; j ++ ) {
 
-				this.add( createTargetMesh() );
-				this.add( createEffectorMesh() );
+						this.add( createLinkMesh() );
 
-				for ( var j = 0, jl = ik.links.length; j < jl; j ++ ) {
+					}
 
-					this.add( createLinkMesh() );
+					this.add( createLine( ik ) );
 
 				}
 
-				this.add( createLine( ik ) );
-
 			}
+		} );
+		return CCDIKSolver;
 
-		}
-
-	} );
+	}();
 
-	return CCDIKSolver;
+	THREE.CCDIKSolver = CCDIKSolver;
 
 } )();

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 546 - 676
examples/js/animation/MMDAnimationHelper.js


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 656 - 813
examples/js/animation/MMDPhysics.js


+ 124 - 160
examples/js/cameras/CinematicCamera.js

@@ -1,200 +1,164 @@
-THREE.CinematicCamera = function ( fov, aspect, near, far ) {
-
-	THREE.PerspectiveCamera.call( this, fov, aspect, near, far );
+( function () {
+
+	var CinematicCamera = function ( fov, aspect, near, far ) {
+
+		THREE.PerspectiveCamera.call( this, fov, aspect, near, far );
+		this.type = 'CinematicCamera';
+		this.postprocessing = {
+			enabled: true
+		};
+		this.shaderSettings = {
+			rings: 3,
+			samples: 4
+		};
+		var depthShader = THREE.BokehDepthShader;
+		this.materialDepth = new THREE.ShaderMaterial( {
+			uniforms: depthShader.uniforms,
+			vertexShader: depthShader.vertexShader,
+			fragmentShader: depthShader.fragmentShader
+		} );
+		this.materialDepth.uniforms[ 'mNear' ].value = near;
+		this.materialDepth.uniforms[ 'mFar' ].value = far; // In case of cinematicCamera, having a default lens set is important
 
-	this.type = 'CinematicCamera';
+		this.setLens();
+		this.initPostProcessing();
 
-	this.postprocessing = { enabled: true };
-	this.shaderSettings = {
-		rings: 3,
-		samples: 4
 	};
 
-	var depthShader = THREE.BokehDepthShader;
-
-	this.materialDepth = new THREE.ShaderMaterial( {
-		uniforms: depthShader.uniforms,
-		vertexShader: depthShader.vertexShader,
-		fragmentShader: depthShader.fragmentShader
-	} );
-
-	this.materialDepth.uniforms[ 'mNear' ].value = near;
-	this.materialDepth.uniforms[ 'mFar' ].value = far;
-
-	// In case of cinematicCamera, having a default lens set is important
-	this.setLens();
-
-	this.initPostProcessing();
-
-};
-
-THREE.CinematicCamera.prototype = Object.create( THREE.PerspectiveCamera.prototype );
-THREE.CinematicCamera.prototype.constructor = THREE.CinematicCamera;
-
-
-// providing fnumber and coc(Circle of Confusion) as extra arguments
-THREE.CinematicCamera.prototype.setLens = function ( focalLength, filmGauge, fNumber, coc ) {
-
-	// In case of cinematicCamera, having a default lens set is important
-	if ( focalLength === undefined ) focalLength = 35;
-	if ( filmGauge !== undefined ) this.filmGauge = filmGauge;
-
-	this.setFocalLength( focalLength );
-
-	// if fnumber and coc are not provided, cinematicCamera tries to act as a basic PerspectiveCamera
-	if ( fNumber === undefined ) fNumber = 8;
-	if ( coc === undefined ) coc = 0.019;
-
-	this.fNumber = fNumber;
-	this.coc = coc;
-
-	// fNumber is focalLength by aperture
-	this.aperture = focalLength / this.fNumber;
-
-	// hyperFocal is required to calculate depthOfField when a lens tries to focus at a distance with given fNumber and focalLength
-	this.hyperFocal = ( focalLength * focalLength ) / ( this.aperture * this.coc );
-
-};
-
-THREE.CinematicCamera.prototype.linearize = function ( depth ) {
-
-	var zfar = this.far;
-	var znear = this.near;
-	return - zfar * znear / ( depth * ( zfar - znear ) - zfar );
-
-};
-
-THREE.CinematicCamera.prototype.smoothstep = function ( near, far, depth ) {
-
-	var x = this.saturate( ( depth - near ) / ( far - near ) );
-	return x * x * ( 3 - 2 * x );
+	CinematicCamera.prototype = Object.create( THREE.PerspectiveCamera.prototype );
+	CinematicCamera.prototype.constructor = CinematicCamera; // providing fnumber and coc(Circle of Confusion) as extra arguments
 
-};
+	CinematicCamera.prototype.setLens = function ( focalLength, filmGauge, fNumber, coc ) {
 
-THREE.CinematicCamera.prototype.saturate = function ( x ) {
+		// In case of cinematicCamera, having a default lens set is important
+		if ( focalLength === undefined ) focalLength = 35;
+		if ( filmGauge !== undefined ) this.filmGauge = filmGauge;
+		this.setFocalLength( focalLength ); // if fnumber and coc are not provided, cinematicCamera tries to act as a basic THREE.PerspectiveCamera
 
-	return Math.max( 0, Math.min( 1, x ) );
+		if ( fNumber === undefined ) fNumber = 8;
+		if ( coc === undefined ) coc = 0.019;
+		this.fNumber = fNumber;
+		this.coc = coc; // fNumber is focalLength by aperture
 
-};
+		this.aperture = focalLength / this.fNumber; // hyperFocal is required to calculate depthOfField when a lens tries to focus at a distance with given fNumber and focalLength
 
-// function for focusing at a distance from the camera
-THREE.CinematicCamera.prototype.focusAt = function ( focusDistance ) {
+		this.hyperFocal = focalLength * focalLength / ( this.aperture * this.coc );
 
-	if ( focusDistance === undefined ) focusDistance = 20;
-
-	var focalLength = this.getFocalLength();
-
-	// distance from the camera (normal to frustrum) to focus on
-	this.focus = focusDistance;
-
-	// the nearest point from the camera which is in focus (unused)
-	this.nearPoint = ( this.hyperFocal * this.focus ) / ( this.hyperFocal + ( this.focus - focalLength ) );
-
-	// the farthest point from the camera which is in focus (unused)
-	this.farPoint = ( this.hyperFocal * this.focus ) / ( this.hyperFocal - ( this.focus - focalLength ) );
-
-	// the gap or width of the space in which is everything is in focus (unused)
-	this.depthOfField = this.farPoint - this.nearPoint;
-
-	// Considering minimum distance of focus for a standard lens (unused)
-	if ( this.depthOfField < 0 ) this.depthOfField = 0;
-
-	this.sdistance = this.smoothstep( this.near, this.far, this.focus );
-
-	this.ldistance = this.linearize( 1 -	this.sdistance );
-
-	this.postprocessing.bokeh_uniforms[ 'focalDepth' ].value = this.ldistance;
-
-};
-
-THREE.CinematicCamera.prototype.initPostProcessing = function () {
-
-	if ( this.postprocessing.enabled ) {
-
-		this.postprocessing.scene = new THREE.Scene();
-
-		this.postprocessing.camera = new THREE.OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2,	window.innerHeight / 2, window.innerHeight / - 2, - 10000, 10000 );
-
-		this.postprocessing.scene.add( this.postprocessing.camera );
+	};
 
-		var pars = { minFilter: THREE.LinearFilter, magFilter: THREE.LinearFilter, format: THREE.RGBFormat };
-		this.postprocessing.rtTextureDepth = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight, pars );
-		this.postprocessing.rtTextureColor = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight, pars );
+	CinematicCamera.prototype.linearize = function ( depth ) {
 
-		var bokeh_shader = THREE.BokehShader;
+		var zfar = this.far;
+		var znear = this.near;
+		return - zfar * znear / ( depth * ( zfar - znear ) - zfar );
 
-		this.postprocessing.bokeh_uniforms = THREE.UniformsUtils.clone( bokeh_shader.uniforms );
+	};
 
-		this.postprocessing.bokeh_uniforms[ 'tColor' ].value = this.postprocessing.rtTextureColor.texture;
-		this.postprocessing.bokeh_uniforms[ 'tDepth' ].value = this.postprocessing.rtTextureDepth.texture;
+	CinematicCamera.prototype.smoothstep = function ( near, far, depth ) {
 
-		this.postprocessing.bokeh_uniforms[ 'manualdof' ].value = 0;
-		this.postprocessing.bokeh_uniforms[ 'shaderFocus' ].value = 0;
+		var x = this.saturate( ( depth - near ) / ( far - near ) );
+		return x * x * ( 3 - 2 * x );
 
-		this.postprocessing.bokeh_uniforms[ 'fstop' ].value = 2.8;
+	};
 
-		this.postprocessing.bokeh_uniforms[ 'showFocus' ].value = 1;
+	CinematicCamera.prototype.saturate = function ( x ) {
 
-		this.postprocessing.bokeh_uniforms[ 'focalDepth' ].value = 0.1;
+		return Math.max( 0, Math.min( 1, x ) );
 
-		//console.log( this.postprocessing.bokeh_uniforms[ "focalDepth" ].value );
+	}; // function for focusing at a distance from the camera
 
-		this.postprocessing.bokeh_uniforms[ 'znear' ].value = this.near;
-		this.postprocessing.bokeh_uniforms[ 'zfar' ].value = this.near;
 
+	CinematicCamera.prototype.focusAt = function ( focusDistance ) {
 
-		this.postprocessing.bokeh_uniforms[ 'textureWidth' ].value = window.innerWidth;
+		if ( focusDistance === undefined ) focusDistance = 20;
+		var focalLength = this.getFocalLength(); // distance from the camera (normal to frustrum) to focus on
 
-		this.postprocessing.bokeh_uniforms[ 'textureHeight' ].value = window.innerHeight;
+		this.focus = focusDistance; // the nearest point from the camera which is in focus (unused)
 
-		this.postprocessing.materialBokeh = new THREE.ShaderMaterial( {
-			uniforms: this.postprocessing.bokeh_uniforms,
-			vertexShader: bokeh_shader.vertexShader,
-			fragmentShader: bokeh_shader.fragmentShader,
-			defines: {
-				RINGS: this.shaderSettings.rings,
-				SAMPLES: this.shaderSettings.samples,
-				DEPTH_PACKING: 1
-			}
-		} );
+		this.nearPoint = this.hyperFocal * this.focus / ( this.hyperFocal + ( this.focus - focalLength ) ); // the farthest point from the camera which is in focus (unused)
 
-		this.postprocessing.quad = new THREE.Mesh( new THREE.PlaneGeometry( window.innerWidth, window.innerHeight ), this.postprocessing.materialBokeh );
-		this.postprocessing.quad.position.z = - 500;
-		this.postprocessing.scene.add( this.postprocessing.quad );
+		this.farPoint = this.hyperFocal * this.focus / ( this.hyperFocal - ( this.focus - focalLength ) ); // the gap or width of the space in which is everything is in focus (unused)
 
-	}
+		this.depthOfField = this.farPoint - this.nearPoint; // Considering minimum distance of focus for a standard lens (unused)
 
-};
+		if ( this.depthOfField < 0 ) this.depthOfField = 0;
+		this.sdistance = this.smoothstep( this.near, this.far, this.focus );
+		this.ldistance = this.linearize( 1 - this.sdistance );
+		this.postprocessing.bokeh_uniforms[ 'focalDepth' ].value = this.ldistance;
 
-THREE.CinematicCamera.prototype.renderCinematic = function ( scene, renderer ) {
+	};
 
-	if ( this.postprocessing.enabled ) {
+	CinematicCamera.prototype.initPostProcessing = function () {
+
+		if ( this.postprocessing.enabled ) {
+
+			this.postprocessing.scene = new THREE.Scene();
+			this.postprocessing.camera = new THREE.OrthographicCamera( window.innerWidth / - 2, window.innerWidth / 2, window.innerHeight / 2, window.innerHeight / - 2, - 10000, 10000 );
+			this.postprocessing.scene.add( this.postprocessing.camera );
+			var pars = {
+				minFilter: THREE.LinearFilter,
+				magFilter: THREE.LinearFilter,
+				format: THREE.RGBFormat
+			};
+			this.postprocessing.rtTextureDepth = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight, pars );
+			this.postprocessing.rtTextureColor = new THREE.WebGLRenderTarget( window.innerWidth, window.innerHeight, pars );
+			var bokeh_shader = THREE.BokehShader;
+			this.postprocessing.bokeh_uniforms = THREE.UniformsUtils.clone( bokeh_shader.uniforms );
+			this.postprocessing.bokeh_uniforms[ 'tColor' ].value = this.postprocessing.rtTextureColor.texture;
+			this.postprocessing.bokeh_uniforms[ 'tDepth' ].value = this.postprocessing.rtTextureDepth.texture;
+			this.postprocessing.bokeh_uniforms[ 'manualdof' ].value = 0;
+			this.postprocessing.bokeh_uniforms[ 'shaderFocus' ].value = 0;
+			this.postprocessing.bokeh_uniforms[ 'fstop' ].value = 2.8;
+			this.postprocessing.bokeh_uniforms[ 'showFocus' ].value = 1;
+			this.postprocessing.bokeh_uniforms[ 'focalDepth' ].value = 0.1; //console.log( this.postprocessing.bokeh_uniforms[ "focalDepth" ].value );
+
+			this.postprocessing.bokeh_uniforms[ 'znear' ].value = this.near;
+			this.postprocessing.bokeh_uniforms[ 'zfar' ].value = this.near;
+			this.postprocessing.bokeh_uniforms[ 'textureWidth' ].value = window.innerWidth;
+			this.postprocessing.bokeh_uniforms[ 'textureHeight' ].value = window.innerHeight;
+			this.postprocessing.materialBokeh = new THREE.ShaderMaterial( {
+				uniforms: this.postprocessing.bokeh_uniforms,
+				vertexShader: bokeh_shader.vertexShader,
+				fragmentShader: bokeh_shader.fragmentShader,
+				defines: {
+					RINGS: this.shaderSettings.rings,
+					SAMPLES: this.shaderSettings.samples,
+					DEPTH_PACKING: 1
+				}
+			} );
+			this.postprocessing.quad = new THREE.Mesh( new THREE.PlaneGeometry( window.innerWidth, window.innerHeight ), this.postprocessing.materialBokeh );
+			this.postprocessing.quad.position.z = - 500;
+			this.postprocessing.scene.add( this.postprocessing.quad );
+
+		}
 
-		var currentRenderTarget = renderer.getRenderTarget();
+	};
 
-		renderer.clear();
+	CinematicCamera.prototype.renderCinematic = function ( scene, renderer ) {
 
-		// Render scene into texture
+		if ( this.postprocessing.enabled ) {
 
-		scene.overrideMaterial = null;
-		renderer.setRenderTarget( this.postprocessing.rtTextureColor );
-		renderer.clear();
-		renderer.render( scene, this );
+			var currentRenderTarget = renderer.getRenderTarget();
+			renderer.clear(); // Render scene into texture
 
-		// Render depth into texture
+			scene.overrideMaterial = null;
+			renderer.setRenderTarget( this.postprocessing.rtTextureColor );
+			renderer.clear();
+			renderer.render( scene, this ); // Render depth into texture
 
-		scene.overrideMaterial = this.materialDepth;
-		renderer.setRenderTarget( this.postprocessing.rtTextureDepth );
-		renderer.clear();
-		renderer.render( scene, this );
+			scene.overrideMaterial = this.materialDepth;
+			renderer.setRenderTarget( this.postprocessing.rtTextureDepth );
+			renderer.clear();
+			renderer.render( scene, this ); // Render bokeh composite
 
-		// Render bokeh composite
+			renderer.setRenderTarget( null );
+			renderer.render( this.postprocessing.scene, this.postprocessing.camera );
+			renderer.setRenderTarget( currentRenderTarget );
 
-		renderer.setRenderTarget( null );
-		renderer.render( this.postprocessing.scene, this.postprocessing.camera );
+		}
 
-		renderer.setRenderTarget( currentRenderTarget );
+	};
 
-	}
+	THREE.CinematicCamera = CinematicCamera;
 
-};
+} )();

+ 84 - 89
examples/js/controls/DeviceOrientationControls.js

@@ -1,155 +1,150 @@
-/**
+( function () {
+
+	/**
  * W3C Device Orientation control (http://w3c.github.io/deviceorientation/spec-source-orientation.html)
  */
 
-THREE.DeviceOrientationControls = function ( object ) {
-
-	if ( window.isSecureContext === false ) {
-
-		console.error( 'THREE.DeviceOrientationControls: DeviceOrientationEvent is only available in secure contexts (https)' );
-
-	}
-
-	var scope = this;
-	var changeEvent = { type: 'change' };
-	var EPS = 0.000001;
-
-	this.object = object;
-	this.object.rotation.reorder( 'YXZ' );
-
-	this.enabled = true;
+	var DeviceOrientationControls = function ( object ) {
 
-	this.deviceOrientation = {};
-	this.screenOrientation = 0;
+		if ( window.isSecureContext === false ) {
 
-	this.alphaOffset = 0; // radians
+			console.error( 'THREE.DeviceOrientationControls: DeviceOrientationEvent is only available in secure contexts (https)' );
 
-	var onDeviceOrientationChangeEvent = function ( event ) {
-
-		scope.deviceOrientation = event;
-
-	};
-
-	var onScreenOrientationChangeEvent = function () {
-
-		scope.screenOrientation = window.orientation || 0;
+		}
 
-	};
+		var scope = this;
+		var changeEvent = {
+			type: 'change'
+		};
+		var EPS = 0.000001;
+		this.object = object;
+		this.object.rotation.reorder( 'YXZ' );
+		this.enabled = true;
+		this.deviceOrientation = {};
+		this.screenOrientation = 0;
+		this.alphaOffset = 0; // radians
 
-	// The angles alpha, beta and gamma form a set of intrinsic Tait-Bryan angles of type Z-X'-Y''
+		var onDeviceOrientationChangeEvent = function ( event ) {
 
-	var setObjectQuaternion = function () {
+			scope.deviceOrientation = event;
 
-		var zee = new THREE.Vector3( 0, 0, 1 );
+		};
 
-		var euler = new THREE.Euler();
+		var onScreenOrientationChangeEvent = function () {
 
-		var q0 = new THREE.Quaternion();
+			scope.screenOrientation = window.orientation || 0;
 
-		var q1 = new THREE.Quaternion( - Math.sqrt( 0.5 ), 0, 0, Math.sqrt( 0.5 ) ); // - PI/2 around the x-axis
+		}; // The angles alpha, beta and gamma form a set of intrinsic Tait-Bryan angles of type Z-X'-Y''
 
-		return function ( quaternion, alpha, beta, gamma, orient ) {
 
-			euler.set( beta, alpha, - gamma, 'YXZ' ); // 'ZXY' for the device, but 'YXZ' for us
+		var setObjectQuaternion = function () {
 
-			quaternion.setFromEuler( euler ); // orient the device
+			var zee = new THREE.Vector3( 0, 0, 1 );
+			var euler = new THREE.Euler();
+			var q0 = new THREE.Quaternion();
+			var q1 = new THREE.Quaternion( - Math.sqrt( 0.5 ), 0, 0, Math.sqrt( 0.5 ) ); // - PI/2 around the x-axis
 
-			quaternion.multiply( q1 ); // camera looks out the back of the device, not the top
+			return function ( quaternion, alpha, beta, gamma, orient ) {
 
-			quaternion.multiply( q0.setFromAxisAngle( zee, - orient ) ); // adjust for screen orientation
+				euler.set( beta, alpha, - gamma, 'YXZ' ); // 'ZXY' for the device, but 'YXZ' for us
 
-		};
+				quaternion.setFromEuler( euler ); // orient the device
 
-	}();
+				quaternion.multiply( q1 ); // camera looks out the back of the device, not the top
 
-	this.connect = function () {
+				quaternion.multiply( q0.setFromAxisAngle( zee, - orient ) ); // adjust for screen orientation
 
-		onScreenOrientationChangeEvent(); // run once on load
+			};
 
-		// iOS 13+
+		}();
 
-		if ( window.DeviceOrientationEvent !== undefined && typeof window.DeviceOrientationEvent.requestPermission === 'function' ) {
+		this.connect = function () {
 
-			window.DeviceOrientationEvent.requestPermission().then( function ( response ) {
+			onScreenOrientationChangeEvent(); // run once on load
+			// iOS 13+
 
-				if ( response == 'granted' ) {
+			if ( window.DeviceOrientationEvent !== undefined && typeof window.DeviceOrientationEvent.requestPermission === 'function' ) {
 
-					window.addEventListener( 'orientationchange', onScreenOrientationChangeEvent );
-					window.addEventListener( 'deviceorientation', onDeviceOrientationChangeEvent );
+				window.DeviceOrientationEvent.requestPermission().then( function ( response ) {
 
-				}
+					if ( response == 'granted' ) {
 
-			} ).catch( function ( error ) {
+						window.addEventListener( 'orientationchange', onScreenOrientationChangeEvent );
+						window.addEventListener( 'deviceorientation', onDeviceOrientationChangeEvent );
 
-				console.error( 'THREE.DeviceOrientationControls: Unable to use DeviceOrientation API:', error );
+					}
 
-			} );
+				} ).catch( function ( error ) {
 
-		} else {
+					console.error( 'THREE.DeviceOrientationControls: Unable to use DeviceOrientation API:', error );
 
-			window.addEventListener( 'orientationchange', onScreenOrientationChangeEvent );
-			window.addEventListener( 'deviceorientation', onDeviceOrientationChangeEvent );
+				} );
 
-		}
+			} else {
 
-		scope.enabled = true;
+				window.addEventListener( 'orientationchange', onScreenOrientationChangeEvent );
+				window.addEventListener( 'deviceorientation', onDeviceOrientationChangeEvent );
 
-	};
+			}
 
-	this.disconnect = function () {
+			scope.enabled = true;
 
-		window.removeEventListener( 'orientationchange', onScreenOrientationChangeEvent );
-		window.removeEventListener( 'deviceorientation', onDeviceOrientationChangeEvent );
+		};
 
-		scope.enabled = false;
+		this.disconnect = function () {
 
-	};
+			window.removeEventListener( 'orientationchange', onScreenOrientationChangeEvent );
+			window.removeEventListener( 'deviceorientation', onDeviceOrientationChangeEvent );
+			scope.enabled = false;
 
-	this.update = ( function () {
+		};
 
-		var lastQuaternion = new THREE.Quaternion();
+		this.update = function () {
 
-		return function () {
+			var lastQuaternion = new THREE.Quaternion();
+			return function () {
 
-			if ( scope.enabled === false ) return;
+				if ( scope.enabled === false ) return;
+				var device = scope.deviceOrientation;
 
-			var device = scope.deviceOrientation;
+				if ( device ) {
 
-			if ( device ) {
+					var alpha = device.alpha ? THREE.MathUtils.degToRad( device.alpha ) + scope.alphaOffset : 0; // Z
 
-				var alpha = device.alpha ? THREE.MathUtils.degToRad( device.alpha ) + scope.alphaOffset : 0; // Z
+					var beta = device.beta ? THREE.MathUtils.degToRad( device.beta ) : 0; // X'
 
-				var beta = device.beta ? THREE.MathUtils.degToRad( device.beta ) : 0; // X'
+					var gamma = device.gamma ? THREE.MathUtils.degToRad( device.gamma ) : 0; // Y''
 
-				var gamma = device.gamma ? THREE.MathUtils.degToRad( device.gamma ) : 0; // Y''
+					var orient = scope.screenOrientation ? THREE.MathUtils.degToRad( scope.screenOrientation ) : 0; // O
 
-				var orient = scope.screenOrientation ? THREE.MathUtils.degToRad( scope.screenOrientation ) : 0; // O
+					setObjectQuaternion( scope.object.quaternion, alpha, beta, gamma, orient );
 
-				setObjectQuaternion( scope.object.quaternion, alpha, beta, gamma, orient );
+					if ( 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
 
-				if ( 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
+						lastQuaternion.copy( scope.object.quaternion );
+						scope.dispatchEvent( changeEvent );
 
-					lastQuaternion.copy( scope.object.quaternion );
-					scope.dispatchEvent( changeEvent );
+					}
 
 				}
 
-			}
+			};
 
-		};
+		}();
 
+		this.dispose = function () {
 
-	} )();
+			scope.disconnect();
 
-	this.dispose = function () {
+		};
 
-		scope.disconnect();
+		this.connect();
 
 	};
 
-	this.connect();
+	DeviceOrientationControls.prototype = Object.create( THREE.EventDispatcher.prototype );
+	DeviceOrientationControls.prototype.constructor = DeviceOrientationControls;
 
-};
+	THREE.DeviceOrientationControls = DeviceOrientationControls;
 
-THREE.DeviceOrientationControls.prototype = Object.create( THREE.EventDispatcher.prototype );
-THREE.DeviceOrientationControls.prototype.constructor = THREE.DeviceOrientationControls;
+} )();

+ 209 - 174
examples/js/controls/DragControls.js

@@ -1,316 +1,351 @@
-THREE.DragControls = function ( _objects, _camera, _domElement ) {
+( function () {
 
-	var _plane = new THREE.Plane();
-	var _raycaster = new THREE.Raycaster();
+	var DragControls = function ( _objects, _camera, _domElement ) {
 
-	var _mouse = new THREE.Vector2();
-	var _offset = new THREE.Vector3();
-	var _intersection = new THREE.Vector3();
-	var _worldPosition = new THREE.Vector3();
-	var _inverseMatrix = new THREE.Matrix4();
-	var _intersections = [];
+		var _plane = new THREE.Plane();
 
-	var _selected = null, _hovered = null;
+		var _raycaster = new THREE.Raycaster();
 
-	//
+		var _mouse = new THREE.Vector2();
 
-	var scope = this;
+		var _offset = new THREE.Vector3();
 
-	function activate() {
+		var _intersection = new THREE.Vector3();
 
-		_domElement.addEventListener( 'pointermove', onPointerMove );
-		_domElement.addEventListener( 'pointerdown', onPointerDown );
-		_domElement.addEventListener( 'pointerup', onPointerCancel );
-		_domElement.addEventListener( 'pointerleave', onPointerCancel );
-		_domElement.addEventListener( 'touchmove', onTouchMove );
-		_domElement.addEventListener( 'touchstart', onTouchStart );
-		_domElement.addEventListener( 'touchend', onTouchEnd );
+		var _worldPosition = new THREE.Vector3();
 
-	}
+		var _inverseMatrix = new THREE.Matrix4();
 
-	function deactivate() {
+		var _intersections = [];
+		var _selected = null,
+			_hovered = null; //
 
-		_domElement.removeEventListener( 'pointermove', onPointerMove );
-		_domElement.removeEventListener( 'pointerdown', onPointerDown );
-		_domElement.removeEventListener( 'pointerup', onPointerCancel );
-		_domElement.removeEventListener( 'pointerleave', onPointerCancel );
-		_domElement.removeEventListener( 'touchmove', onTouchMove );
-		_domElement.removeEventListener( 'touchstart', onTouchStart );
-		_domElement.removeEventListener( 'touchend', onTouchEnd );
+		var scope = this;
 
-		_domElement.style.cursor = '';
+		function activate() {
 
-	}
+			_domElement.addEventListener( 'pointermove', onPointerMove );
 
-	function dispose() {
+			_domElement.addEventListener( 'pointerdown', onPointerDown );
 
-		deactivate();
+			_domElement.addEventListener( 'pointerup', onPointerCancel );
 
-	}
+			_domElement.addEventListener( 'pointerleave', onPointerCancel );
 
-	function getObjects() {
+			_domElement.addEventListener( 'touchmove', onTouchMove );
 
-		return _objects;
+			_domElement.addEventListener( 'touchstart', onTouchStart );
 
-	}
+			_domElement.addEventListener( 'touchend', onTouchEnd );
 
-	function onPointerMove( event ) {
+		}
 
-		event.preventDefault();
+		function deactivate() {
 
-		switch ( event.pointerType ) {
+			_domElement.removeEventListener( 'pointermove', onPointerMove );
 
-			case 'mouse':
-			case 'pen':
-				onMouseMove( event );
-				break;
+			_domElement.removeEventListener( 'pointerdown', onPointerDown );
 
-			// TODO touch
+			_domElement.removeEventListener( 'pointerup', onPointerCancel );
+
+			_domElement.removeEventListener( 'pointerleave', onPointerCancel );
+
+			_domElement.removeEventListener( 'touchmove', onTouchMove );
+
+			_domElement.removeEventListener( 'touchstart', onTouchStart );
+
+			_domElement.removeEventListener( 'touchend', onTouchEnd );
+
+			_domElement.style.cursor = '';
 
 		}
 
-	}
+		function dispose() {
 
-	function onMouseMove( event ) {
+			deactivate();
 
-		var rect = _domElement.getBoundingClientRect();
+		}
 
-		_mouse.x = ( ( event.clientX - rect.left ) / rect.width ) * 2 - 1;
-		_mouse.y = - ( ( event.clientY - rect.top ) / rect.height ) * 2 + 1;
+		function getObjects() {
 
-		_raycaster.setFromCamera( _mouse, _camera );
+			return _objects;
 
-		if ( _selected && scope.enabled ) {
+		}
 
-			if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {
+		function onPointerMove( event ) {
 
-				_selected.position.copy( _intersection.sub( _offset ).applyMatrix4( _inverseMatrix ) );
+			event.preventDefault();
 
-			}
+			switch ( event.pointerType ) {
 
-			scope.dispatchEvent( { type: 'drag', object: _selected } );
+				case 'mouse':
+				case 'pen':
+					onMouseMove( event );
+					break;
+			// TODO touch
 
-			return;
+			}
 
 		}
 
-		_intersections.length = 0;
+		function onMouseMove( event ) {
+
+			var rect = _domElement.getBoundingClientRect();
 
-		_raycaster.setFromCamera( _mouse, _camera );
-		_raycaster.intersectObjects( _objects, true, _intersections );
+			_mouse.x = ( event.clientX - rect.left ) / rect.width * 2 - 1;
+			_mouse.y = - ( ( event.clientY - rect.top ) / rect.height ) * 2 + 1;
 
-		if ( _intersections.length > 0 ) {
+			_raycaster.setFromCamera( _mouse, _camera );
 
-			var object = _intersections[ 0 ].object;
+			if ( _selected && scope.enabled ) {
 
-			_plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( object.matrixWorld ) );
+				if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {
 
-			if ( _hovered !== object && _hovered !== null ) {
+					_selected.position.copy( _intersection.sub( _offset ).applyMatrix4( _inverseMatrix ) );
 
-				scope.dispatchEvent( { type: 'hoveroff', object: _hovered } );
+				}
 
-				_domElement.style.cursor = 'auto';
-				_hovered = null;
+				scope.dispatchEvent( {
+					type: 'drag',
+					object: _selected
+				} );
+				return;
 
 			}
 
-			if ( _hovered !== object ) {
+			_intersections.length = 0;
 
-				scope.dispatchEvent( { type: 'hoveron', object: object } );
+			_raycaster.setFromCamera( _mouse, _camera );
 
-				_domElement.style.cursor = 'pointer';
-				_hovered = object;
+			_raycaster.intersectObjects( _objects, true, _intersections );
 
-			}
+			if ( _intersections.length > 0 ) {
 
-		} else {
+				var object = _intersections[ 0 ].object;
 
-			if ( _hovered !== null ) {
+				_plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( object.matrixWorld ) );
 
-				scope.dispatchEvent( { type: 'hoveroff', object: _hovered } );
+				if ( _hovered !== object && _hovered !== null ) {
 
-				_domElement.style.cursor = 'auto';
-				_hovered = null;
+					scope.dispatchEvent( {
+						type: 'hoveroff',
+						object: _hovered
+					} );
+					_domElement.style.cursor = 'auto';
+					_hovered = null;
 
-			}
+				}
 
-		}
+				if ( _hovered !== object ) {
 
-	}
+					scope.dispatchEvent( {
+						type: 'hoveron',
+						object: object
+					} );
+					_domElement.style.cursor = 'pointer';
+					_hovered = object;
 
-	function onPointerDown( event ) {
+				}
 
-		event.preventDefault();
+			} else {
 
-		switch ( event.pointerType ) {
+				if ( _hovered !== null ) {
 
-			case 'mouse':
-			case 'pen':
-				onMouseDown( event );
-				break;
+					scope.dispatchEvent( {
+						type: 'hoveroff',
+						object: _hovered
+					} );
+					_domElement.style.cursor = 'auto';
+					_hovered = null;
 
-			// TODO touch
+				}
+
+			}
 
 		}
 
-	}
+		function onPointerDown( event ) {
+
+			event.preventDefault();
 
-	function onMouseDown( event ) {
+			switch ( event.pointerType ) {
 
-		event.preventDefault();
+				case 'mouse':
+				case 'pen':
+					onMouseDown( event );
+					break;
+			// TODO touch
 
-		_intersections.length = 0;
+			}
 
-		_raycaster.setFromCamera( _mouse, _camera );
-		_raycaster.intersectObjects( _objects, true, _intersections );
+		}
 
-		if ( _intersections.length > 0 ) {
+		function onMouseDown( event ) {
 
-			_selected = ( scope.transformGroup === true ) ? _objects[ 0 ] : _intersections[ 0 ].object;
+			event.preventDefault();
+			_intersections.length = 0;
 
-			if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {
+			_raycaster.setFromCamera( _mouse, _camera );
 
-				_inverseMatrix.copy( _selected.parent.matrixWorld ).invert();
-				_offset.copy( _intersection ).sub( _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
+			_raycaster.intersectObjects( _objects, true, _intersections );
 
-			}
+			if ( _intersections.length > 0 ) {
 
-			_domElement.style.cursor = 'move';
+				_selected = scope.transformGroup === true ? _objects[ 0 ] : _intersections[ 0 ].object;
 
-			scope.dispatchEvent( { type: 'dragstart', object: _selected } );
+				if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {
 
-		}
+					_inverseMatrix.copy( _selected.parent.matrixWorld ).invert();
 
+					_offset.copy( _intersection ).sub( _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
 
-	}
+				}
 
-	function onPointerCancel( event ) {
+				_domElement.style.cursor = 'move';
+				scope.dispatchEvent( {
+					type: 'dragstart',
+					object: _selected
+				} );
 
-		event.preventDefault();
+			}
+
+		}
 
-		switch ( event.pointerType ) {
+		function onPointerCancel( event ) {
 
-			case 'mouse':
-			case 'pen':
-				onMouseCancel( event );
-				break;
+			event.preventDefault();
 
+			switch ( event.pointerType ) {
+
+				case 'mouse':
+				case 'pen':
+					onMouseCancel( event );
+					break;
 			// TODO touch
 
+			}
+
 		}
 
-	}
+		function onMouseCancel( event ) {
 
-	function onMouseCancel( event ) {
+			event.preventDefault();
 
-		event.preventDefault();
+			if ( _selected ) {
 
-		if ( _selected ) {
+				scope.dispatchEvent( {
+					type: 'dragend',
+					object: _selected
+				} );
+				_selected = null;
 
-			scope.dispatchEvent( { type: 'dragend', object: _selected } );
+			}
 
-			_selected = null;
+			_domElement.style.cursor = _hovered ? 'pointer' : 'auto';
 
 		}
 
-		_domElement.style.cursor = _hovered ? 'pointer' : 'auto';
+		function onTouchMove( event ) {
 
-	}
+			event.preventDefault();
+			event = event.changedTouches[ 0 ];
 
-	function onTouchMove( event ) {
+			var rect = _domElement.getBoundingClientRect();
 
-		event.preventDefault();
-		event = event.changedTouches[ 0 ];
+			_mouse.x = ( event.clientX - rect.left ) / rect.width * 2 - 1;
+			_mouse.y = - ( ( event.clientY - rect.top ) / rect.height ) * 2 + 1;
 
-		var rect = _domElement.getBoundingClientRect();
+			_raycaster.setFromCamera( _mouse, _camera );
 
-		_mouse.x = ( ( event.clientX - rect.left ) / rect.width ) * 2 - 1;
-		_mouse.y = - ( ( event.clientY - rect.top ) / rect.height ) * 2 + 1;
+			if ( _selected && scope.enabled ) {
 
-		_raycaster.setFromCamera( _mouse, _camera );
+				if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {
 
-		if ( _selected && scope.enabled ) {
+					_selected.position.copy( _intersection.sub( _offset ).applyMatrix4( _inverseMatrix ) );
 
-			if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {
+				}
 
-				_selected.position.copy( _intersection.sub( _offset ).applyMatrix4( _inverseMatrix ) );
+				scope.dispatchEvent( {
+					type: 'drag',
+					object: _selected
+				} );
+				return;
 
 			}
 
-			scope.dispatchEvent( { type: 'drag', object: _selected } );
-
-			return;
-
 		}
 
-	}
+		function onTouchStart( event ) {
 
-	function onTouchStart( event ) {
+			event.preventDefault();
+			event = event.changedTouches[ 0 ];
 
-		event.preventDefault();
-		event = event.changedTouches[ 0 ];
+			var rect = _domElement.getBoundingClientRect();
 
-		var rect = _domElement.getBoundingClientRect();
+			_mouse.x = ( event.clientX - rect.left ) / rect.width * 2 - 1;
+			_mouse.y = - ( ( event.clientY - rect.top ) / rect.height ) * 2 + 1;
+			_intersections.length = 0;
 
-		_mouse.x = ( ( event.clientX - rect.left ) / rect.width ) * 2 - 1;
-		_mouse.y = - ( ( event.clientY - rect.top ) / rect.height ) * 2 + 1;
+			_raycaster.setFromCamera( _mouse, _camera );
 
-		_intersections.length = 0;
+			_raycaster.intersectObjects( _objects, true, _intersections );
 
-		_raycaster.setFromCamera( _mouse, _camera );
-		 _raycaster.intersectObjects( _objects, true, _intersections );
+			if ( _intersections.length > 0 ) {
 
-		if ( _intersections.length > 0 ) {
+				_selected = scope.transformGroup === true ? _objects[ 0 ] : _intersections[ 0 ].object;
 
-			_selected = ( scope.transformGroup === true ) ? _objects[ 0 ] : _intersections[ 0 ].object;
+				_plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
 
-			_plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
+				if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {
 
-			if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {
+					_inverseMatrix.copy( _selected.parent.matrixWorld ).invert();
 
-				_inverseMatrix.copy( _selected.parent.matrixWorld ).invert();
-				_offset.copy( _intersection ).sub( _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
+					_offset.copy( _intersection ).sub( _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
 
-			}
+				}
 
-			_domElement.style.cursor = 'move';
+				_domElement.style.cursor = 'move';
+				scope.dispatchEvent( {
+					type: 'dragstart',
+					object: _selected
+				} );
 
-			scope.dispatchEvent( { type: 'dragstart', object: _selected } );
+			}
 
 		}
 
+		function onTouchEnd( event ) {
 
-	}
-
-	function onTouchEnd( event ) {
+			event.preventDefault();
 
-		event.preventDefault();
+			if ( _selected ) {
 
-		if ( _selected ) {
+				scope.dispatchEvent( {
+					type: 'dragend',
+					object: _selected
+				} );
+				_selected = null;
 
-			scope.dispatchEvent( { type: 'dragend', object: _selected } );
+			}
 
-			_selected = null;
+			_domElement.style.cursor = 'auto';
 
 		}
 
-		_domElement.style.cursor = 'auto';
-
-	}
-
-	activate();
+		activate(); // API
 
-	// API
+		this.enabled = true;
+		this.transformGroup = false;
+		this.activate = activate;
+		this.deactivate = deactivate;
+		this.dispose = dispose;
+		this.getObjects = getObjects;
 
-	this.enabled = true;
-	this.transformGroup = false;
+	};
 
-	this.activate = activate;
-	this.deactivate = deactivate;
-	this.dispose = dispose;
-	this.getObjects = getObjects;
+	DragControls.prototype = Object.create( THREE.EventDispatcher.prototype );
+	DragControls.prototype.constructor = DragControls;
 
-};
+	THREE.DragControls = DragControls;
 
-THREE.DragControls.prototype = Object.create( THREE.EventDispatcher.prototype );
-THREE.DragControls.prototype.constructor = THREE.DragControls;
+} )();

+ 229 - 220
examples/js/controls/FirstPersonControls.js

@@ -1,344 +1,353 @@
-THREE.FirstPersonControls = function ( object, domElement ) {
+( function () {
 
-	if ( domElement === undefined ) {
+	var FirstPersonControls = function ( object, domElement ) {
 
-		console.warn( 'THREE.FirstPersonControls: The second parameter "domElement" is now mandatory.' );
-		domElement = document;
+		if ( domElement === undefined ) {
 
-	}
+			console.warn( 'THREE.FirstPersonControls: The second parameter "domElement" is now mandatory.' );
+			domElement = document;
 
-	this.object = object;
-	this.domElement = domElement;
-
-	// API
-
-	this.enabled = true;
-
-	this.movementSpeed = 1.0;
-	this.lookSpeed = 0.005;
-
-	this.lookVertical = true;
-	this.autoForward = false;
+		}
 
-	this.activeLook = true;
+		this.object = object;
+		this.domElement = domElement; // API
+
+		this.enabled = true;
+		this.movementSpeed = 1.0;
+		this.lookSpeed = 0.005;
+		this.lookVertical = true;
+		this.autoForward = false;
+		this.activeLook = true;
+		this.heightSpeed = false;
+		this.heightCoef = 1.0;
+		this.heightMin = 0.0;
+		this.heightMax = 1.0;
+		this.constrainVertical = false;
+		this.verticalMin = 0;
+		this.verticalMax = Math.PI;
+		this.mouseDragOn = false; // internals
+
+		this.autoSpeedFactor = 0.0;
+		this.mouseX = 0;
+		this.mouseY = 0;
+		this.moveForward = false;
+		this.moveBackward = false;
+		this.moveLeft = false;
+		this.moveRight = false;
+		this.viewHalfX = 0;
+		this.viewHalfY = 0; // private variables
+
+		var lat = 0;
+		var lon = 0;
+		var lookDirection = new THREE.Vector3();
+		var spherical = new THREE.Spherical();
+		var target = new THREE.Vector3(); //
 
-	this.heightSpeed = false;
-	this.heightCoef = 1.0;
-	this.heightMin = 0.0;
-	this.heightMax = 1.0;
+		if ( this.domElement !== document ) {
 
-	this.constrainVertical = false;
-	this.verticalMin = 0;
-	this.verticalMax = Math.PI;
+			this.domElement.setAttribute( 'tabindex', - 1 );
 
-	this.mouseDragOn = false;
+		} //
 
-	// internals
 
-	this.autoSpeedFactor = 0.0;
+		this.handleResize = function () {
 
-	this.mouseX = 0;
-	this.mouseY = 0;
+			if ( this.domElement === document ) {
 
-	this.moveForward = false;
-	this.moveBackward = false;
-	this.moveLeft = false;
-	this.moveRight = false;
+				this.viewHalfX = window.innerWidth / 2;
+				this.viewHalfY = window.innerHeight / 2;
 
-	this.viewHalfX = 0;
-	this.viewHalfY = 0;
+			} else {
 
-	// private variables
+				this.viewHalfX = this.domElement.offsetWidth / 2;
+				this.viewHalfY = this.domElement.offsetHeight / 2;
 
-	var lat = 0;
-	var lon = 0;
+			}
 
-	var lookDirection = new THREE.Vector3();
-	var spherical = new THREE.Spherical();
-	var target = new THREE.Vector3();
+		};
 
-	//
+		this.onMouseDown = function ( event ) {
 
-	if ( this.domElement !== document ) {
+			if ( this.domElement !== document ) {
 
-		this.domElement.setAttribute( 'tabindex', - 1 );
+				this.domElement.focus();
 
-	}
+			}
 
-	//
+			event.preventDefault();
 
-	this.handleResize = function () {
+			if ( this.activeLook ) {
 
-		if ( this.domElement === document ) {
+				switch ( event.button ) {
 
-			this.viewHalfX = window.innerWidth / 2;
-			this.viewHalfY = window.innerHeight / 2;
+					case 0:
+						this.moveForward = true;
+						break;
 
-		} else {
+					case 2:
+						this.moveBackward = true;
+						break;
 
-			this.viewHalfX = this.domElement.offsetWidth / 2;
-			this.viewHalfY = this.domElement.offsetHeight / 2;
+				}
 
-		}
+			}
 
-	};
+			this.mouseDragOn = true;
 
-	this.onMouseDown = function ( event ) {
+		};
 
-		if ( this.domElement !== document ) {
+		this.onMouseUp = function ( event ) {
 
-			this.domElement.focus();
+			event.preventDefault();
 
-		}
+			if ( this.activeLook ) {
 
-		event.preventDefault();
+				switch ( event.button ) {
 
-		if ( this.activeLook ) {
+					case 0:
+						this.moveForward = false;
+						break;
 
-			switch ( event.button ) {
+					case 2:
+						this.moveBackward = false;
+						break;
 
-				case 0: this.moveForward = true; break;
-				case 2: this.moveBackward = true; break;
+				}
 
 			}
 
-		}
-
-		this.mouseDragOn = true;
+			this.mouseDragOn = false;
 
-	};
+		};
 
-	this.onMouseUp = function ( event ) {
+		this.onMouseMove = function ( event ) {
 
-		event.preventDefault();
+			if ( this.domElement === document ) {
 
-		if ( this.activeLook ) {
+				this.mouseX = event.pageX - this.viewHalfX;
+				this.mouseY = event.pageY - this.viewHalfY;
 
-			switch ( event.button ) {
+			} else {
 
-				case 0: this.moveForward = false; break;
-				case 2: this.moveBackward = false; break;
+				this.mouseX = event.pageX - this.domElement.offsetLeft - this.viewHalfX;
+				this.mouseY = event.pageY - this.domElement.offsetTop - this.viewHalfY;
 
 			}
 
-		}
-
-		this.mouseDragOn = false;
-
-	};
-
-	this.onMouseMove = function ( event ) {
-
-		if ( this.domElement === document ) {
-
-			this.mouseX = event.pageX - this.viewHalfX;
-			this.mouseY = event.pageY - this.viewHalfY;
-
-		} else {
-
-			this.mouseX = event.pageX - this.domElement.offsetLeft - this.viewHalfX;
-			this.mouseY = event.pageY - this.domElement.offsetTop - this.viewHalfY;
-
-		}
-
-	};
-
-	this.onKeyDown = function ( event ) {
-
-		//event.preventDefault();
+		};
 
-		switch ( event.code ) {
+		this.onKeyDown = function ( event ) {
 
-			case 'ArrowUp':
-			case 'KeyW': this.moveForward = true; break;
+			//event.preventDefault();
+			switch ( event.code ) {
 
-			case 'ArrowLeft':
-			case 'KeyA': this.moveLeft = true; break;
+				case 'ArrowUp':
+				case 'KeyW':
+					this.moveForward = true;
+					break;
 
-			case 'ArrowDown':
-			case 'KeyS': this.moveBackward = true; break;
+				case 'ArrowLeft':
+				case 'KeyA':
+					this.moveLeft = true;
+					break;
 
-			case 'ArrowRight':
-			case 'KeyD': this.moveRight = true; break;
+				case 'ArrowDown':
+				case 'KeyS':
+					this.moveBackward = true;
+					break;
 
-			case 'KeyR': this.moveUp = true; break;
-			case 'KeyF': this.moveDown = true; break;
+				case 'ArrowRight':
+				case 'KeyD':
+					this.moveRight = true;
+					break;
 
-		}
+				case 'KeyR':
+					this.moveUp = true;
+					break;
 
-	};
+				case 'KeyF':
+					this.moveDown = true;
+					break;
 
-	this.onKeyUp = function ( event ) {
+			}
 
-		switch ( event.code ) {
+		};
 
-			case 'ArrowUp':
-			case 'KeyW': this.moveForward = false; break;
+		this.onKeyUp = function ( event ) {
 
-			case 'ArrowLeft':
-			case 'KeyA': this.moveLeft = false; break;
+			switch ( event.code ) {
 
-			case 'ArrowDown':
-			case 'KeyS': this.moveBackward = false; break;
+				case 'ArrowUp':
+				case 'KeyW':
+					this.moveForward = false;
+					break;
 
-			case 'ArrowRight':
-			case 'KeyD': this.moveRight = false; break;
+				case 'ArrowLeft':
+				case 'KeyA':
+					this.moveLeft = false;
+					break;
 
-			case 'KeyR': this.moveUp = false; break;
-			case 'KeyF': this.moveDown = false; break;
+				case 'ArrowDown':
+				case 'KeyS':
+					this.moveBackward = false;
+					break;
 
-		}
+				case 'ArrowRight':
+				case 'KeyD':
+					this.moveRight = false;
+					break;
 
-	};
+				case 'KeyR':
+					this.moveUp = false;
+					break;
 
-	this.lookAt = function ( x, y, z ) {
+				case 'KeyF':
+					this.moveDown = false;
+					break;
 
-		if ( x.isVector3 ) {
+			}
 
-			target.copy( x );
+		};
 
-		} else {
+		this.lookAt = function ( x, y, z ) {
 
-			target.set( x, y, z );
+			if ( x.isVector3 ) {
 
-		}
+				target.copy( x );
 
-		this.object.lookAt( target );
+			} else {
 
-		setOrientation( this );
+				target.set( x, y, z );
 
-		return this;
+			}
 
-	};
+			this.object.lookAt( target );
+			setOrientation( this );
+			return this;
 
-	this.update = function () {
+		};
 
-		var targetPosition = new THREE.Vector3();
+		this.update = function () {
 
-		return function update( delta ) {
+			var targetPosition = new THREE.Vector3();
+			return function update( delta ) {
 
-			if ( this.enabled === false ) return;
+				if ( this.enabled === false ) return;
 
-			if ( this.heightSpeed ) {
+				if ( this.heightSpeed ) {
 
-				var y = THREE.MathUtils.clamp( this.object.position.y, this.heightMin, this.heightMax );
-				var heightDelta = y - this.heightMin;
+					var y = THREE.MathUtils.clamp( this.object.position.y, this.heightMin, this.heightMax );
+					var heightDelta = y - this.heightMin;
+					this.autoSpeedFactor = delta * ( heightDelta * this.heightCoef );
 
-				this.autoSpeedFactor = delta * ( heightDelta * this.heightCoef );
+				} else {
 
-			} else {
+					this.autoSpeedFactor = 0.0;
 
-				this.autoSpeedFactor = 0.0;
+				}
 
-			}
+				var actualMoveSpeed = delta * this.movementSpeed;
+				if ( this.moveForward || this.autoForward && ! this.moveBackward ) this.object.translateZ( - ( actualMoveSpeed + this.autoSpeedFactor ) );
+				if ( this.moveBackward ) this.object.translateZ( actualMoveSpeed );
+				if ( this.moveLeft ) this.object.translateX( - actualMoveSpeed );
+				if ( this.moveRight ) this.object.translateX( actualMoveSpeed );
+				if ( this.moveUp ) this.object.translateY( actualMoveSpeed );
+				if ( this.moveDown ) this.object.translateY( - actualMoveSpeed );
+				var actualLookSpeed = delta * this.lookSpeed;
 
-			var actualMoveSpeed = delta * this.movementSpeed;
+				if ( ! this.activeLook ) {
 
-			if ( this.moveForward || ( this.autoForward && ! this.moveBackward ) ) this.object.translateZ( - ( actualMoveSpeed + this.autoSpeedFactor ) );
-			if ( this.moveBackward ) this.object.translateZ( actualMoveSpeed );
+					actualLookSpeed = 0;
 
-			if ( this.moveLeft ) this.object.translateX( - actualMoveSpeed );
-			if ( this.moveRight ) this.object.translateX( actualMoveSpeed );
+				}
 
-			if ( this.moveUp ) this.object.translateY( actualMoveSpeed );
-			if ( this.moveDown ) this.object.translateY( - actualMoveSpeed );
+				var verticalLookRatio = 1;
 
-			var actualLookSpeed = delta * this.lookSpeed;
+				if ( this.constrainVertical ) {
 
-			if ( ! this.activeLook ) {
+					verticalLookRatio = Math.PI / ( this.verticalMax - this.verticalMin );
 
-				actualLookSpeed = 0;
+				}
 
-			}
+				lon -= this.mouseX * actualLookSpeed;
+				if ( this.lookVertical ) lat -= this.mouseY * actualLookSpeed * verticalLookRatio;
+				lat = Math.max( - 85, Math.min( 85, lat ) );
+				var phi = THREE.MathUtils.degToRad( 90 - lat );
+				var theta = THREE.MathUtils.degToRad( lon );
 
-			var verticalLookRatio = 1;
+				if ( this.constrainVertical ) {
 
-			if ( this.constrainVertical ) {
+					phi = THREE.MathUtils.mapLinear( phi, 0, Math.PI, this.verticalMin, this.verticalMax );
 
-				verticalLookRatio = Math.PI / ( this.verticalMax - this.verticalMin );
+				}
 
-			}
-
-			lon -= this.mouseX * actualLookSpeed;
-			if ( this.lookVertical ) lat -= this.mouseY * actualLookSpeed * verticalLookRatio;
+				var position = this.object.position;
+				targetPosition.setFromSphericalCoords( 1, phi, theta ).add( position );
+				this.object.lookAt( targetPosition );
 
-			lat = Math.max( - 85, Math.min( 85, lat ) );
+			};
 
-			var phi = THREE.MathUtils.degToRad( 90 - lat );
-			var theta = THREE.MathUtils.degToRad( lon );
+		}();
 
-			if ( this.constrainVertical ) {
+		function contextmenu( event ) {
 
-				phi = THREE.MathUtils.mapLinear( phi, 0, Math.PI, this.verticalMin, this.verticalMax );
+			event.preventDefault();
 
-			}
-
-			var position = this.object.position;
+		}
 
-			targetPosition.setFromSphericalCoords( 1, phi, theta ).add( position );
+		this.dispose = function () {
 
-			this.object.lookAt( targetPosition );
+			this.domElement.removeEventListener( 'contextmenu', contextmenu );
+			this.domElement.removeEventListener( 'mousedown', _onMouseDown );
+			this.domElement.removeEventListener( 'mousemove', _onMouseMove );
+			this.domElement.removeEventListener( 'mouseup', _onMouseUp );
+			window.removeEventListener( 'keydown', _onKeyDown );
+			window.removeEventListener( 'keyup', _onKeyUp );
 
 		};
 
-	}();
+		var _onMouseMove = bind( this, this.onMouseMove );
 
-	function contextmenu( event ) {
+		var _onMouseDown = bind( this, this.onMouseDown );
 
-		event.preventDefault();
+		var _onMouseUp = bind( this, this.onMouseUp );
 
-	}
+		var _onKeyDown = bind( this, this.onKeyDown );
 
-	this.dispose = function () {
-
-		this.domElement.removeEventListener( 'contextmenu', contextmenu );
-		this.domElement.removeEventListener( 'mousedown', _onMouseDown );
-		this.domElement.removeEventListener( 'mousemove', _onMouseMove );
-		this.domElement.removeEventListener( 'mouseup', _onMouseUp );
-
-		window.removeEventListener( 'keydown', _onKeyDown );
-		window.removeEventListener( 'keyup', _onKeyUp );
-
-	};
+		var _onKeyUp = bind( this, this.onKeyUp );
 
-	var _onMouseMove = bind( this, this.onMouseMove );
-	var _onMouseDown = bind( this, this.onMouseDown );
-	var _onMouseUp = bind( this, this.onMouseUp );
-	var _onKeyDown = bind( this, this.onKeyDown );
-	var _onKeyUp = bind( this, this.onKeyUp );
+		this.domElement.addEventListener( 'contextmenu', contextmenu );
+		this.domElement.addEventListener( 'mousemove', _onMouseMove );
+		this.domElement.addEventListener( 'mousedown', _onMouseDown );
+		this.domElement.addEventListener( 'mouseup', _onMouseUp );
+		window.addEventListener( 'keydown', _onKeyDown );
+		window.addEventListener( 'keyup', _onKeyUp );
 
-	this.domElement.addEventListener( 'contextmenu', contextmenu );
-	this.domElement.addEventListener( 'mousemove', _onMouseMove );
-	this.domElement.addEventListener( 'mousedown', _onMouseDown );
-	this.domElement.addEventListener( 'mouseup', _onMouseUp );
+		function bind( scope, fn ) {
 
-	window.addEventListener( 'keydown', _onKeyDown );
-	window.addEventListener( 'keyup', _onKeyUp );
+			return function () {
 
-	function bind( scope, fn ) {
+				fn.apply( scope, arguments );
 
-		return function () {
+			};
 
-			fn.apply( scope, arguments );
-
-		};
-
-	}
-
-	function setOrientation( controls ) {
+		}
 
-		var quaternion = controls.object.quaternion;
+		function setOrientation( controls ) {
 
-		lookDirection.set( 0, 0, - 1 ).applyQuaternion( quaternion );
-		spherical.setFromVector3( lookDirection );
+			var quaternion = controls.object.quaternion;
+			lookDirection.set( 0, 0, - 1 ).applyQuaternion( quaternion );
+			spherical.setFromVector3( lookDirection );
+			lat = 90 - THREE.MathUtils.radToDeg( spherical.phi );
+			lon = THREE.MathUtils.radToDeg( spherical.theta );
 
-		lat = 90 - THREE.MathUtils.radToDeg( spherical.phi );
-		lon = THREE.MathUtils.radToDeg( spherical.theta );
+		}
 
-	}
+		this.handleResize();
+		setOrientation( this );
 
-	this.handleResize();
+	};
 
-	setOrientation( this );
+	THREE.FirstPersonControls = FirstPersonControls;
 
-};
+} )();

+ 255 - 185
examples/js/controls/FlyControls.js

@@ -1,307 +1,377 @@
-THREE.FlyControls = function ( object, domElement ) {
+( function () {
 
-	if ( domElement === undefined ) {
+	var FlyControls = function ( object, domElement ) {
 
-		console.warn( 'THREE.FlyControls: The second parameter "domElement" is now mandatory.' );
-		domElement = document;
+		if ( domElement === undefined ) {
 
-	}
+			console.warn( 'THREE.FlyControls: The second parameter "domElement" is now mandatory.' );
+			domElement = document;
 
-	this.object = object;
-	this.domElement = domElement;
+		}
 
-	if ( domElement ) this.domElement.setAttribute( 'tabindex', - 1 );
+		this.object = object;
+		this.domElement = domElement;
+		if ( domElement ) this.domElement.setAttribute( 'tabindex', - 1 ); // API
 
-	// API
+		this.movementSpeed = 1.0;
+		this.rollSpeed = 0.005;
+		this.dragToLook = false;
+		this.autoForward = false; // disable default target object behavior
+		// internals
 
-	this.movementSpeed = 1.0;
-	this.rollSpeed = 0.005;
+		var scope = this;
+		var changeEvent = {
+			type: 'change'
+		};
+		var EPS = 0.000001;
+		this.tmpQuaternion = new THREE.Quaternion();
+		this.mouseStatus = 0;
+		this.moveState = {
+			up: 0,
+			down: 0,
+			left: 0,
+			right: 0,
+			forward: 0,
+			back: 0,
+			pitchUp: 0,
+			pitchDown: 0,
+			yawLeft: 0,
+			yawRight: 0,
+			rollLeft: 0,
+			rollRight: 0
+		};
+		this.moveVector = new THREE.Vector3( 0, 0, 0 );
+		this.rotationVector = new THREE.Vector3( 0, 0, 0 );
 
-	this.dragToLook = false;
-	this.autoForward = false;
+		this.keydown = function ( event ) {
 
-	// disable default target object behavior
+			if ( event.altKey ) {
 
-	// internals
+				return;
 
-	var scope = this;
-	var changeEvent = { type: 'change' };
-	var EPS = 0.000001;
+			} //event.preventDefault();
 
-	this.tmpQuaternion = new THREE.Quaternion();
 
-	this.mouseStatus = 0;
+			switch ( event.code ) {
 
-	this.moveState = { up: 0, down: 0, left: 0, right: 0, forward: 0, back: 0, pitchUp: 0, pitchDown: 0, yawLeft: 0, yawRight: 0, rollLeft: 0, rollRight: 0 };
-	this.moveVector = new THREE.Vector3( 0, 0, 0 );
-	this.rotationVector = new THREE.Vector3( 0, 0, 0 );
+				case 'ShiftLeft':
+				case 'ShiftRight':
+					this.movementSpeedMultiplier = .1;
+					break;
 
-	this.keydown = function ( event ) {
+				case 'KeyW':
+					this.moveState.forward = 1;
+					break;
 
-		if ( event.altKey ) {
+				case 'KeyS':
+					this.moveState.back = 1;
+					break;
 
-			return;
+				case 'KeyA':
+					this.moveState.left = 1;
+					break;
 
-		}
+				case 'KeyD':
+					this.moveState.right = 1;
+					break;
 
-		//event.preventDefault();
+				case 'KeyR':
+					this.moveState.up = 1;
+					break;
 
-		switch ( event.code ) {
+				case 'KeyF':
+					this.moveState.down = 1;
+					break;
 
-			case 'ShiftLeft':
-			case 'ShiftRight': this.movementSpeedMultiplier = .1; break;
+				case 'ArrowUp':
+					this.moveState.pitchUp = 1;
+					break;
 
-			case 'KeyW': this.moveState.forward = 1; break;
-			case 'KeyS': this.moveState.back = 1; break;
+				case 'ArrowDown':
+					this.moveState.pitchDown = 1;
+					break;
 
-			case 'KeyA': this.moveState.left = 1; break;
-			case 'KeyD': this.moveState.right = 1; break;
+				case 'ArrowLeft':
+					this.moveState.yawLeft = 1;
+					break;
 
-			case 'KeyR': this.moveState.up = 1; break;
-			case 'KeyF': this.moveState.down = 1; break;
+				case 'ArrowRight':
+					this.moveState.yawRight = 1;
+					break;
 
-			case 'ArrowUp': this.moveState.pitchUp = 1; break;
-			case 'ArrowDown': this.moveState.pitchDown = 1; break;
+				case 'KeyQ':
+					this.moveState.rollLeft = 1;
+					break;
 
-			case 'ArrowLeft': this.moveState.yawLeft = 1; break;
-			case 'ArrowRight': this.moveState.yawRight = 1; break;
+				case 'KeyE':
+					this.moveState.rollRight = 1;
+					break;
 
-			case 'KeyQ': this.moveState.rollLeft = 1; break;
-			case 'KeyE': this.moveState.rollRight = 1; break;
+			}
 
-		}
+			this.updateMovementVector();
+			this.updateRotationVector();
 
-		this.updateMovementVector();
-		this.updateRotationVector();
+		};
 
-	};
+		this.keyup = function ( event ) {
 
-	this.keyup = function ( event ) {
+			switch ( event.code ) {
 
-		switch ( event.code ) {
+				case 'ShiftLeft':
+				case 'ShiftRight':
+					this.movementSpeedMultiplier = 1;
+					break;
 
-			case 'ShiftLeft':
-			case 'ShiftRight': this.movementSpeedMultiplier = 1; break;
+				case 'KeyW':
+					this.moveState.forward = 0;
+					break;
 
-			case 'KeyW': this.moveState.forward = 0; break;
-			case 'KeyS': this.moveState.back = 0; break;
+				case 'KeyS':
+					this.moveState.back = 0;
+					break;
 
-			case 'KeyA': this.moveState.left = 0; break;
-			case 'KeyD': this.moveState.right = 0; break;
+				case 'KeyA':
+					this.moveState.left = 0;
+					break;
 
-			case 'KeyR': this.moveState.up = 0; break;
-			case 'KeyF': this.moveState.down = 0; break;
+				case 'KeyD':
+					this.moveState.right = 0;
+					break;
 
-			case 'ArrowUp': this.moveState.pitchUp = 0; break;
-			case 'ArrowDown': this.moveState.pitchDown = 0; break;
+				case 'KeyR':
+					this.moveState.up = 0;
+					break;
 
-			case 'ArrowLeft': this.moveState.yawLeft = 0; break;
-			case 'ArrowRight': this.moveState.yawRight = 0; break;
+				case 'KeyF':
+					this.moveState.down = 0;
+					break;
 
-			case 'KeyQ': this.moveState.rollLeft = 0; break;
-			case 'KeyE': this.moveState.rollRight = 0; break;
+				case 'ArrowUp':
+					this.moveState.pitchUp = 0;
+					break;
 
-		}
+				case 'ArrowDown':
+					this.moveState.pitchDown = 0;
+					break;
 
-		this.updateMovementVector();
-		this.updateRotationVector();
+				case 'ArrowLeft':
+					this.moveState.yawLeft = 0;
+					break;
 
-	};
+				case 'ArrowRight':
+					this.moveState.yawRight = 0;
+					break;
 
-	this.mousedown = function ( event ) {
+				case 'KeyQ':
+					this.moveState.rollLeft = 0;
+					break;
 
-		if ( this.domElement !== document ) {
+				case 'KeyE':
+					this.moveState.rollRight = 0;
+					break;
 
-			this.domElement.focus();
+			}
 
-		}
+			this.updateMovementVector();
+			this.updateRotationVector();
 
-		event.preventDefault();
+		};
 
-		if ( this.dragToLook ) {
+		this.mousedown = function ( event ) {
 
-			this.mouseStatus ++;
+			if ( this.domElement !== document ) {
 
-		} else {
+				this.domElement.focus();
 
-			switch ( event.button ) {
+			}
 
-				case 0: this.moveState.forward = 1; break;
-				case 2: this.moveState.back = 1; break;
+			event.preventDefault();
 
-			}
+			if ( this.dragToLook ) {
 
-			this.updateMovementVector();
+				this.mouseStatus ++;
 
-		}
+			} else {
 
-	};
+				switch ( event.button ) {
 
-	this.mousemove = function ( event ) {
+					case 0:
+						this.moveState.forward = 1;
+						break;
 
-		if ( ! this.dragToLook || this.mouseStatus > 0 ) {
+					case 2:
+						this.moveState.back = 1;
+						break;
 
-			var container = this.getContainerDimensions();
-			var halfWidth = container.size[ 0 ] / 2;
-			var halfHeight = container.size[ 1 ] / 2;
+				}
 
-			this.moveState.yawLeft = - ( ( event.pageX - container.offset[ 0 ] ) - halfWidth ) / halfWidth;
-			this.moveState.pitchDown = ( ( event.pageY - container.offset[ 1 ] ) - halfHeight ) / halfHeight;
+				this.updateMovementVector();
 
-			this.updateRotationVector();
+			}
 
-		}
+		};
 
-	};
+		this.mousemove = function ( event ) {
 
-	this.mouseup = function ( event ) {
+			if ( ! this.dragToLook || this.mouseStatus > 0 ) {
 
-		event.preventDefault();
+				var container = this.getContainerDimensions();
+				var halfWidth = container.size[ 0 ] / 2;
+				var halfHeight = container.size[ 1 ] / 2;
+				this.moveState.yawLeft = - ( event.pageX - container.offset[ 0 ] - halfWidth ) / halfWidth;
+				this.moveState.pitchDown = ( event.pageY - container.offset[ 1 ] - halfHeight ) / halfHeight;
+				this.updateRotationVector();
 
-		if ( this.dragToLook ) {
+			}
 
-			this.mouseStatus --;
+		};
 
-			this.moveState.yawLeft = this.moveState.pitchDown = 0;
+		this.mouseup = function ( event ) {
 
-		} else {
+			event.preventDefault();
 
-			switch ( event.button ) {
+			if ( this.dragToLook ) {
 
-				case 0: this.moveState.forward = 0; break;
-				case 2: this.moveState.back = 0; break;
+				this.mouseStatus --;
+				this.moveState.yawLeft = this.moveState.pitchDown = 0;
 
-			}
+			} else {
 
-			this.updateMovementVector();
+				switch ( event.button ) {
 
-		}
+					case 0:
+						this.moveState.forward = 0;
+						break;
 
-		this.updateRotationVector();
+					case 2:
+						this.moveState.back = 0;
+						break;
 
-	};
+				}
 
-	this.update = function () {
+				this.updateMovementVector();
 
-		var lastQuaternion = new THREE.Quaternion();
-		var lastPosition = new THREE.Vector3();
+			}
 
-		return function ( delta ) {
+			this.updateRotationVector();
 
-			var moveMult = delta * scope.movementSpeed;
-			var rotMult = delta * scope.rollSpeed;
+		};
 
-			scope.object.translateX( scope.moveVector.x * moveMult );
-			scope.object.translateY( scope.moveVector.y * moveMult );
-			scope.object.translateZ( scope.moveVector.z * moveMult );
+		this.update = function () {
 
-			scope.tmpQuaternion.set( scope.rotationVector.x * rotMult, scope.rotationVector.y * rotMult, scope.rotationVector.z * rotMult, 1 ).normalize();
-			scope.object.quaternion.multiply( scope.tmpQuaternion );
+			var lastQuaternion = new THREE.Quaternion();
+			var lastPosition = new THREE.Vector3();
+			return function ( delta ) {
 
-			if (
-				lastPosition.distanceToSquared( scope.object.position ) > EPS ||
-				8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS
-			) {
+				var moveMult = delta * scope.movementSpeed;
+				var rotMult = delta * scope.rollSpeed;
+				scope.object.translateX( scope.moveVector.x * moveMult );
+				scope.object.translateY( scope.moveVector.y * moveMult );
+				scope.object.translateZ( scope.moveVector.z * moveMult );
+				scope.tmpQuaternion.set( scope.rotationVector.x * rotMult, scope.rotationVector.y * rotMult, scope.rotationVector.z * rotMult, 1 ).normalize();
+				scope.object.quaternion.multiply( scope.tmpQuaternion );
 
-				scope.dispatchEvent( changeEvent );
-				lastQuaternion.copy( scope.object.quaternion );
-				lastPosition.copy( scope.object.position );
+				if ( lastPosition.distanceToSquared( scope.object.position ) > EPS || 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
 
-			}
+					scope.dispatchEvent( changeEvent );
+					lastQuaternion.copy( scope.object.quaternion );
+					lastPosition.copy( scope.object.position );
 
-		};
+				}
 
-	}();
+			};
 
-	this.updateMovementVector = function () {
+		}();
 
-		var forward = ( this.moveState.forward || ( this.autoForward && ! this.moveState.back ) ) ? 1 : 0;
+		this.updateMovementVector = function () {
 
-		this.moveVector.x = ( - this.moveState.left + this.moveState.right );
-		this.moveVector.y = ( - this.moveState.down + this.moveState.up );
-		this.moveVector.z = ( - forward + this.moveState.back );
+			var forward = this.moveState.forward || this.autoForward && ! this.moveState.back ? 1 : 0;
+			this.moveVector.x = - this.moveState.left + this.moveState.right;
+			this.moveVector.y = - this.moveState.down + this.moveState.up;
+			this.moveVector.z = - forward + this.moveState.back; //console.log( 'move:', [ this.moveVector.x, this.moveVector.y, this.moveVector.z ] );
 
-		//console.log( 'move:', [ this.moveVector.x, this.moveVector.y, this.moveVector.z ] );
+		};
 
-	};
+		this.updateRotationVector = function () {
 
-	this.updateRotationVector = function () {
+			this.rotationVector.x = - this.moveState.pitchDown + this.moveState.pitchUp;
+			this.rotationVector.y = - this.moveState.yawRight + this.moveState.yawLeft;
+			this.rotationVector.z = - this.moveState.rollRight + this.moveState.rollLeft; //console.log( 'rotate:', [ this.rotationVector.x, this.rotationVector.y, this.rotationVector.z ] );
 
-		this.rotationVector.x = ( - this.moveState.pitchDown + this.moveState.pitchUp );
-		this.rotationVector.y = ( - this.moveState.yawRight + this.moveState.yawLeft );
-		this.rotationVector.z = ( - this.moveState.rollRight + this.moveState.rollLeft );
+		};
 
-		//console.log( 'rotate:', [ this.rotationVector.x, this.rotationVector.y, this.rotationVector.z ] );
+		this.getContainerDimensions = function () {
 
-	};
+			if ( this.domElement != document ) {
 
-	this.getContainerDimensions = function () {
+				return {
+					size: [ this.domElement.offsetWidth, this.domElement.offsetHeight ],
+					offset: [ this.domElement.offsetLeft, this.domElement.offsetTop ]
+				};
 
-		if ( this.domElement != document ) {
+			} else {
 
-			return {
-				size: [ this.domElement.offsetWidth, this.domElement.offsetHeight ],
-				offset: [ this.domElement.offsetLeft, this.domElement.offsetTop ]
-			};
+				return {
+					size: [ window.innerWidth, window.innerHeight ],
+					offset: [ 0, 0 ]
+				};
 
-		} else {
+			}
 
-			return {
-				size: [ window.innerWidth, window.innerHeight ],
-				offset: [ 0, 0 ]
-			};
+		};
 
-		}
+		function bind( scope, fn ) {
 
-	};
+			return function () {
 
-	function bind( scope, fn ) {
+				fn.apply( scope, arguments );
 
-		return function () {
+			};
 
-			fn.apply( scope, arguments );
+		}
 
-		};
+		function contextmenu( event ) {
 
-	}
+			event.preventDefault();
 
-	function contextmenu( event ) {
+		}
 
-		event.preventDefault();
+		this.dispose = function () {
 
-	}
+			this.domElement.removeEventListener( 'contextmenu', contextmenu );
+			this.domElement.removeEventListener( 'mousedown', _mousedown );
+			this.domElement.removeEventListener( 'mousemove', _mousemove );
+			this.domElement.removeEventListener( 'mouseup', _mouseup );
+			window.removeEventListener( 'keydown', _keydown );
+			window.removeEventListener( 'keyup', _keyup );
 
-	this.dispose = function () {
+		};
 
-		this.domElement.removeEventListener( 'contextmenu', contextmenu );
-		this.domElement.removeEventListener( 'mousedown', _mousedown );
-		this.domElement.removeEventListener( 'mousemove', _mousemove );
-		this.domElement.removeEventListener( 'mouseup', _mouseup );
+		var _mousemove = bind( this, this.mousemove );
 
-		window.removeEventListener( 'keydown', _keydown );
-		window.removeEventListener( 'keyup', _keyup );
+		var _mousedown = bind( this, this.mousedown );
 
-	};
+		var _mouseup = bind( this, this.mouseup );
 
-	var _mousemove = bind( this, this.mousemove );
-	var _mousedown = bind( this, this.mousedown );
-	var _mouseup = bind( this, this.mouseup );
-	var _keydown = bind( this, this.keydown );
-	var _keyup = bind( this, this.keyup );
+		var _keydown = bind( this, this.keydown );
 
-	this.domElement.addEventListener( 'contextmenu', contextmenu );
+		var _keyup = bind( this, this.keyup );
 
-	this.domElement.addEventListener( 'mousemove', _mousemove );
-	this.domElement.addEventListener( 'mousedown', _mousedown );
-	this.domElement.addEventListener( 'mouseup', _mouseup );
+		this.domElement.addEventListener( 'contextmenu', contextmenu );
+		this.domElement.addEventListener( 'mousemove', _mousemove );
+		this.domElement.addEventListener( 'mousedown', _mousedown );
+		this.domElement.addEventListener( 'mouseup', _mouseup );
+		window.addEventListener( 'keydown', _keydown );
+		window.addEventListener( 'keyup', _keyup );
+		this.updateMovementVector();
+		this.updateRotationVector();
 
-	window.addEventListener( 'keydown', _keydown );
-	window.addEventListener( 'keyup', _keyup );
+	};
 
-	this.updateMovementVector();
-	this.updateRotationVector();
+	FlyControls.prototype = Object.create( THREE.EventDispatcher.prototype );
+	FlyControls.prototype.constructor = FlyControls;
 
-};
+	THREE.FlyControls = FlyControls;
 
-THREE.FlyControls.prototype = Object.create( THREE.EventDispatcher.prototype );
-THREE.FlyControls.prototype.constructor = THREE.FlyControls;
+} )();

+ 642 - 823
examples/js/controls/OrbitControls.js

@@ -1,1215 +1,1034 @@
-// This set of controls performs orbiting, dollying (zooming), and panning.
-// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
-//
-//    Orbit - left mouse / touch: one-finger move
-//    Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
-//    Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
+( function () {
 
-THREE.OrbitControls = function ( object, domElement ) {
-
-	if ( domElement === undefined ) console.warn( 'THREE.OrbitControls: The second parameter "domElement" is now mandatory.' );
-	if ( domElement === document ) console.error( 'THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' );
-
-	this.object = object;
-	this.domElement = domElement;
-
-	// Set to false to disable this control
-	this.enabled = true;
-
-	// "target" sets the location of focus, where the object orbits around
-	this.target = new THREE.Vector3();
+	// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
+	//
+	//		Orbit - left mouse / touch: one-finger move
+	//		Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
+	//		Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
 
-	// How far you can dolly in and out ( PerspectiveCamera only )
-	this.minDistance = 0;
-	this.maxDistance = Infinity;
+	var OrbitControls = function ( object, domElement ) {
 
-	// How far you can zoom in and out ( OrthographicCamera only )
-	this.minZoom = 0;
-	this.maxZoom = Infinity;
+		if ( domElement === undefined ) console.warn( 'THREE.OrbitControls: The second parameter "domElement" is now mandatory.' );
+		if ( domElement === document ) console.error( 'THREE.OrbitControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' );
+		this.object = object;
+		this.domElement = domElement; // Set to false to disable this control
 
-	// How far you can orbit vertically, upper and lower limits.
-	// Range is 0 to Math.PI radians.
-	this.minPolarAngle = 0; // radians
-	this.maxPolarAngle = Math.PI; // radians
+		this.enabled = true; // "target" sets the location of focus, where the object orbits around
 
-	// How far you can orbit horizontally, upper and lower limits.
-	// If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI )
-	this.minAzimuthAngle = - Infinity; // radians
-	this.maxAzimuthAngle = Infinity; // radians
+		this.target = new THREE.Vector3(); // How far you can dolly in and out ( PerspectiveCamera only )
 
-	// Set to true to enable damping (inertia)
-	// If damping is enabled, you must call controls.update() in your animation loop
-	this.enableDamping = false;
-	this.dampingFactor = 0.05;
+		this.minDistance = 0;
+		this.maxDistance = Infinity; // How far you can zoom in and out ( OrthographicCamera only )
 
-	// This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
-	// Set to false to disable zooming
-	this.enableZoom = true;
-	this.zoomSpeed = 1.0;
+		this.minZoom = 0;
+		this.maxZoom = Infinity; // How far you can orbit vertically, upper and lower limits.
+		// Range is 0 to Math.PI radians.
 
-	// Set to false to disable rotating
-	this.enableRotate = true;
-	this.rotateSpeed = 1.0;
+		this.minPolarAngle = 0; // radians
 
-	// Set to false to disable panning
-	this.enablePan = true;
-	this.panSpeed = 1.0;
-	this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up
-	this.keyPanSpeed = 7.0;	// pixels moved per arrow key push
+		this.maxPolarAngle = Math.PI; // radians
+		// How far you can orbit horizontally, upper and lower limits.
+		// If set, the interval [ min, max ] must be a sub-interval of [ - 2 PI, 2 PI ], with ( max - min < 2 PI )
 
-	// Set to true to automatically rotate around the target
-	// If auto-rotate is enabled, you must call controls.update() in your animation loop
-	this.autoRotate = false;
-	this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60
+		this.minAzimuthAngle = - Infinity; // radians
 
-	// The four arrow keys
-	this.keys = { LEFT: 'ArrowLeft', UP: 'ArrowUp', RIGHT: 'ArrowRight', BOTTOM: 'ArrowDown' };
+		this.maxAzimuthAngle = Infinity; // radians
+		// Set to true to enable damping (inertia)
+		// If damping is enabled, you must call controls.update() in your animation loop
 
-	// Mouse buttons
-	this.mouseButtons = { LEFT: THREE.MOUSE.ROTATE, MIDDLE: THREE.MOUSE.DOLLY, RIGHT: THREE.MOUSE.PAN };
+		this.enableDamping = false;
+		this.dampingFactor = 0.05; // This option actually enables dollying in and out; left as "zoom" for backwards compatibility.
+		// Set to false to disable zooming
 
-	// Touch fingers
-	this.touches = { ONE: THREE.TOUCH.ROTATE, TWO: THREE.TOUCH.DOLLY_PAN };
+		this.enableZoom = true;
+		this.zoomSpeed = 1.0; // Set to false to disable rotating
 
-	// for reset
-	this.target0 = this.target.clone();
-	this.position0 = this.object.position.clone();
-	this.zoom0 = this.object.zoom;
+		this.enableRotate = true;
+		this.rotateSpeed = 1.0; // Set to false to disable panning
 
-	// the target DOM element for key events
-	this._domElementKeyEvents = null;
+		this.enablePan = true;
+		this.panSpeed = 1.0;
+		this.screenSpacePanning = true; // if false, pan orthogonal to world-space direction camera.up
 
-	//
-	// public methods
-	//
+		this.keyPanSpeed = 7.0; // pixels moved per arrow key push
+		// Set to true to automatically rotate around the target
+		// If auto-rotate is enabled, you must call controls.update() in your animation loop
 
-	this.getPolarAngle = function () {
+		this.autoRotate = false;
+		this.autoRotateSpeed = 2.0; // 30 seconds per orbit when fps is 60
+		// The four arrow keys
 
-		return spherical.phi;
+		this.keys = {
+			LEFT: 'ArrowLeft',
+			UP: 'ArrowUp',
+			RIGHT: 'ArrowRight',
+			BOTTOM: 'ArrowDown'
+		}; // Mouse buttons
 
-	};
+		this.mouseButtons = {
+			LEFT: THREE.MOUSE.ROTATE,
+			MIDDLE: THREE.MOUSE.DOLLY,
+			RIGHT: THREE.MOUSE.PAN
+		}; // Touch fingers
 
-	this.getAzimuthalAngle = function () {
+		this.touches = {
+			ONE: THREE.TOUCH.ROTATE,
+			TWO: THREE.TOUCH.DOLLY_PAN
+		}; // for reset
 
-		return spherical.theta;
+		this.target0 = this.target.clone();
+		this.position0 = this.object.position.clone();
+		this.zoom0 = this.object.zoom; // the target DOM element for key events
 
-	};
+		this._domElementKeyEvents = null; //
+		// public methods
+		//
 
-	this.listenToKeyEvents = function ( domElement ) {
+		this.getPolarAngle = function () {
 
-		domElement.addEventListener( 'keydown', onKeyDown );
-		this._domElementKeyEvents = domElement;
+			return spherical.phi;
 
-	};
+		};
 
-	this.saveState = function () {
+		this.getAzimuthalAngle = function () {
 
-		scope.target0.copy( scope.target );
-		scope.position0.copy( scope.object.position );
-		scope.zoom0 = scope.object.zoom;
+			return spherical.theta;
 
-	};
+		};
 
-	this.reset = function () {
+		this.listenToKeyEvents = function ( domElement ) {
 
-		scope.target.copy( scope.target0 );
-		scope.object.position.copy( scope.position0 );
-		scope.object.zoom = scope.zoom0;
+			domElement.addEventListener( 'keydown', onKeyDown );
+			this._domElementKeyEvents = domElement;
 
-		scope.object.updateProjectionMatrix();
-		scope.dispatchEvent( changeEvent );
+		};
 
-		scope.update();
+		this.saveState = function () {
 
-		state = STATE.NONE;
+			scope.target0.copy( scope.target );
+			scope.position0.copy( scope.object.position );
+			scope.zoom0 = scope.object.zoom;
 
-	};
-
-	// this method is exposed, but perhaps it would be better if we can make it private...
-	this.update = function () {
+		};
 
-		var offset = new THREE.Vector3();
+		this.reset = function () {
 
-		// so camera.up is the orbit axis
-		var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
-		var quatInverse = quat.clone().invert();
+			scope.target.copy( scope.target0 );
+			scope.object.position.copy( scope.position0 );
+			scope.object.zoom = scope.zoom0;
+			scope.object.updateProjectionMatrix();
+			scope.dispatchEvent( changeEvent );
+			scope.update();
+			state = STATE.NONE;
 
-		var lastPosition = new THREE.Vector3();
-		var lastQuaternion = new THREE.Quaternion();
+		}; // this method is exposed, but perhaps it would be better if we can make it private...
 
-		var twoPI = 2 * Math.PI;
 
-		return function update() {
+		this.update = function () {
 
-			var position = scope.object.position;
+			var offset = new THREE.Vector3(); // so camera.up is the orbit axis
 
-			offset.copy( position ).sub( scope.target );
+			var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
+			var quatInverse = quat.clone().invert();
+			var lastPosition = new THREE.Vector3();
+			var lastQuaternion = new THREE.Quaternion();
+			var twoPI = 2 * Math.PI;
+			return function update() {
 
-			// rotate offset to "y-axis-is-up" space
-			offset.applyQuaternion( quat );
+				var position = scope.object.position;
+				offset.copy( position ).sub( scope.target ); // rotate offset to "y-axis-is-up" space
 
-			// angle from z-axis around y-axis
-			spherical.setFromVector3( offset );
+				offset.applyQuaternion( quat ); // angle from z-axis around y-axis
 
-			if ( scope.autoRotate && state === STATE.NONE ) {
+				spherical.setFromVector3( offset );
 
-				rotateLeft( getAutoRotationAngle() );
+				if ( scope.autoRotate && state === STATE.NONE ) {
 
-			}
+					rotateLeft( getAutoRotationAngle() );
 
-			if ( scope.enableDamping ) {
+				}
 
-				spherical.theta += sphericalDelta.theta * scope.dampingFactor;
-				spherical.phi += sphericalDelta.phi * scope.dampingFactor;
+				if ( scope.enableDamping ) {
 
-			} else {
+					spherical.theta += sphericalDelta.theta * scope.dampingFactor;
+					spherical.phi += sphericalDelta.phi * scope.dampingFactor;
 
-				spherical.theta += sphericalDelta.theta;
-				spherical.phi += sphericalDelta.phi;
+				} else {
 
-			}
+					spherical.theta += sphericalDelta.theta;
+					spherical.phi += sphericalDelta.phi;
 
-			// restrict theta to be between desired limits
+				} // restrict theta to be between desired limits
 
-			var min = scope.minAzimuthAngle;
-			var max = scope.maxAzimuthAngle;
 
-			if ( isFinite( min ) && isFinite( max ) ) {
+				var min = scope.minAzimuthAngle;
+				var max = scope.maxAzimuthAngle;
 
-				if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI;
+				if ( isFinite( min ) && isFinite( max ) ) {
 
-				if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI;
+					if ( min < - Math.PI ) min += twoPI; else if ( min > Math.PI ) min -= twoPI;
+					if ( max < - Math.PI ) max += twoPI; else if ( max > Math.PI ) max -= twoPI;
 
-				if ( min <= max ) {
+					if ( min <= max ) {
 
-					spherical.theta = Math.max( min, Math.min( max, spherical.theta ) );
+						spherical.theta = Math.max( min, Math.min( max, spherical.theta ) );
 
-				} else {
+					} else {
 
-					spherical.theta = ( spherical.theta > ( min + max ) / 2 ) ?
-						Math.max( min, spherical.theta ) :
-						Math.min( max, spherical.theta );
+						spherical.theta = spherical.theta > ( min + max ) / 2 ? Math.max( min, spherical.theta ) : Math.min( max, spherical.theta );
 
-				}
+					}
 
-			}
+				} // restrict phi to be between desired limits
 
-			// restrict phi to be between desired limits
-			spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
 
-			spherical.makeSafe();
+				spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
+				spherical.makeSafe();
+				spherical.radius *= scale; // restrict radius to be between desired limits
 
+				spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) ); // move target to panned location
 
-			spherical.radius *= scale;
+				if ( scope.enableDamping === true ) {
 
-			// restrict radius to be between desired limits
-			spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) );
+					scope.target.addScaledVector( panOffset, scope.dampingFactor );
 
-			// move target to panned location
+				} else {
 
-			if ( scope.enableDamping === true ) {
+					scope.target.add( panOffset );
 
-				scope.target.addScaledVector( panOffset, scope.dampingFactor );
+				}
 
-			} else {
+				offset.setFromSpherical( spherical ); // rotate offset back to "camera-up-vector-is-up" space
 
-				scope.target.add( panOffset );
+				offset.applyQuaternion( quatInverse );
+				position.copy( scope.target ).add( offset );
+				scope.object.lookAt( scope.target );
 
-			}
+				if ( scope.enableDamping === true ) {
 
-			offset.setFromSpherical( spherical );
+					sphericalDelta.theta *= 1 - scope.dampingFactor;
+					sphericalDelta.phi *= 1 - scope.dampingFactor;
+					panOffset.multiplyScalar( 1 - scope.dampingFactor );
 
-			// rotate offset back to "camera-up-vector-is-up" space
-			offset.applyQuaternion( quatInverse );
+				} else {
 
-			position.copy( scope.target ).add( offset );
+					sphericalDelta.set( 0, 0, 0 );
+					panOffset.set( 0, 0, 0 );
 
-			scope.object.lookAt( scope.target );
+				}
 
-			if ( scope.enableDamping === true ) {
+				scale = 1; // update condition is:
+				// min(camera displacement, camera rotation in radians)^2 > EPS
+				// using small-angle approximation cos(x/2) = 1 - x^2 / 8
 
-				sphericalDelta.theta *= ( 1 - scope.dampingFactor );
-				sphericalDelta.phi *= ( 1 - scope.dampingFactor );
+				if ( zoomChanged || lastPosition.distanceToSquared( scope.object.position ) > EPS || 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
 
-				panOffset.multiplyScalar( 1 - scope.dampingFactor );
+					scope.dispatchEvent( changeEvent );
+					lastPosition.copy( scope.object.position );
+					lastQuaternion.copy( scope.object.quaternion );
+					zoomChanged = false;
+					return true;
 
-			} else {
+				}
 
-				sphericalDelta.set( 0, 0, 0 );
+				return false;
 
-				panOffset.set( 0, 0, 0 );
+			};
 
-			}
+		}();
 
-			scale = 1;
+		this.dispose = function () {
 
-			// update condition is:
-			// min(camera displacement, camera rotation in radians)^2 > EPS
-			// using small-angle approximation cos(x/2) = 1 - x^2 / 8
+			scope.domElement.removeEventListener( 'contextmenu', onContextMenu );
+			scope.domElement.removeEventListener( 'pointerdown', onPointerDown );
+			scope.domElement.removeEventListener( 'wheel', onMouseWheel );
+			scope.domElement.removeEventListener( 'touchstart', onTouchStart );
+			scope.domElement.removeEventListener( 'touchend', onTouchEnd );
+			scope.domElement.removeEventListener( 'touchmove', onTouchMove );
+			scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
+			scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
 
-			if ( zoomChanged ||
-				lastPosition.distanceToSquared( scope.object.position ) > EPS ||
-				8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
+			if ( scope._domElementKeyEvents !== null ) {
 
-				scope.dispatchEvent( changeEvent );
+				scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown );
 
-				lastPosition.copy( scope.object.position );
-				lastQuaternion.copy( scope.object.quaternion );
-				zoomChanged = false;
+			} //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
 
-				return true;
+		}; //
+		// internals
+		//
 
-			}
-
-			return false;
 
+		var scope = this;
+		var changeEvent = {
+			type: 'change'
+		};
+		var startEvent = {
+			type: 'start'
+		};
+		var endEvent = {
+			type: 'end'
 		};
+		var STATE = {
+			NONE: - 1,
+			ROTATE: 0,
+			DOLLY: 1,
+			PAN: 2,
+			TOUCH_ROTATE: 3,
+			TOUCH_PAN: 4,
+			TOUCH_DOLLY_PAN: 5,
+			TOUCH_DOLLY_ROTATE: 6
+		};
+		var state = STATE.NONE;
+		var EPS = 0.000001; // current position in spherical coordinates
+
+		var spherical = new THREE.Spherical();
+		var sphericalDelta = new THREE.Spherical();
+		var scale = 1;
+		var panOffset = new THREE.Vector3();
+		var zoomChanged = false;
+		var rotateStart = new THREE.Vector2();
+		var rotateEnd = new THREE.Vector2();
+		var rotateDelta = new THREE.Vector2();
+		var panStart = new THREE.Vector2();
+		var panEnd = new THREE.Vector2();
+		var panDelta = new THREE.Vector2();
+		var dollyStart = new THREE.Vector2();
+		var dollyEnd = new THREE.Vector2();
+		var dollyDelta = new THREE.Vector2();
+
+		function getAutoRotationAngle() {
+
+			return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
 
-	}();
+		}
 
-	this.dispose = function () {
+		function getZoomScale() {
 
-		scope.domElement.removeEventListener( 'contextmenu', onContextMenu );
+			return Math.pow( 0.95, scope.zoomSpeed );
 
-		scope.domElement.removeEventListener( 'pointerdown', onPointerDown );
-		scope.domElement.removeEventListener( 'wheel', onMouseWheel );
+		}
 
-		scope.domElement.removeEventListener( 'touchstart', onTouchStart );
-		scope.domElement.removeEventListener( 'touchend', onTouchEnd );
-		scope.domElement.removeEventListener( 'touchmove', onTouchMove );
+		function rotateLeft( angle ) {
 
-		scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
-		scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
+			sphericalDelta.theta -= angle;
 
+		}
 
-		if ( scope._domElementKeyEvents !== null ) {
+		function rotateUp( angle ) {
 
-			scope._domElementKeyEvents.removeEventListener( 'keydown', onKeyDown );
+			sphericalDelta.phi -= angle;
 
 		}
 
-		//scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
+		var panLeft = function () {
 
-	};
+			var v = new THREE.Vector3();
+			return function panLeft( distance, objectMatrix ) {
 
-	//
-	// internals
-	//
+				v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
 
-	var scope = this;
-
-	var changeEvent = { type: 'change' };
-	var startEvent = { type: 'start' };
-	var endEvent = { type: 'end' };
-
-	var STATE = {
-		NONE: - 1,
-		ROTATE: 0,
-		DOLLY: 1,
-		PAN: 2,
-		TOUCH_ROTATE: 3,
-		TOUCH_PAN: 4,
-		TOUCH_DOLLY_PAN: 5,
-		TOUCH_DOLLY_ROTATE: 6
-	};
+				v.multiplyScalar( - distance );
+				panOffset.add( v );
 
-	var state = STATE.NONE;
+			};
 
-	var EPS = 0.000001;
+		}();
 
-	// current position in spherical coordinates
-	var spherical = new THREE.Spherical();
-	var sphericalDelta = new THREE.Spherical();
+		var panUp = function () {
 
-	var scale = 1;
-	var panOffset = new THREE.Vector3();
-	var zoomChanged = false;
+			var v = new THREE.Vector3();
+			return function panUp( distance, objectMatrix ) {
 
-	var rotateStart = new THREE.Vector2();
-	var rotateEnd = new THREE.Vector2();
-	var rotateDelta = new THREE.Vector2();
+				if ( scope.screenSpacePanning === true ) {
 
-	var panStart = new THREE.Vector2();
-	var panEnd = new THREE.Vector2();
-	var panDelta = new THREE.Vector2();
+					v.setFromMatrixColumn( objectMatrix, 1 );
 
-	var dollyStart = new THREE.Vector2();
-	var dollyEnd = new THREE.Vector2();
-	var dollyDelta = new THREE.Vector2();
+				} else {
 
-	function getAutoRotationAngle() {
+					v.setFromMatrixColumn( objectMatrix, 0 );
+					v.crossVectors( scope.object.up, v );
 
-		return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
+				}
 
-	}
+				v.multiplyScalar( distance );
+				panOffset.add( v );
 
-	function getZoomScale() {
+			};
 
-		return Math.pow( 0.95, scope.zoomSpeed );
+		}(); // deltaX and deltaY are in pixels; right and down are positive
 
-	}
 
-	function rotateLeft( angle ) {
+		var pan = function () {
 
-		sphericalDelta.theta -= angle;
+			var offset = new THREE.Vector3();
+			return function pan( deltaX, deltaY ) {
 
-	}
+				var element = scope.domElement;
 
-	function rotateUp( angle ) {
+				if ( scope.object.isPerspectiveCamera ) {
 
-		sphericalDelta.phi -= angle;
+					// perspective
+					var position = scope.object.position;
+					offset.copy( position ).sub( scope.target );
+					var targetDistance = offset.length(); // half of the fov is center to top of screen
 
-	}
+					targetDistance *= Math.tan( scope.object.fov / 2 * Math.PI / 180.0 ); // we use only clientHeight here so aspect ratio does not distort speed
 
-	var panLeft = function () {
+					panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
+					panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
 
-		var v = new THREE.Vector3();
+				} else if ( scope.object.isOrthographicCamera ) {
 
-		return function panLeft( distance, objectMatrix ) {
+					// orthographic
+					panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
+					panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
 
-			v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
-			v.multiplyScalar( - distance );
+				} else {
 
-			panOffset.add( v );
+					// camera neither orthographic nor perspective
+					console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
+					scope.enablePan = false;
 
-		};
+				}
+
+			};
 
-	}();
+		}();
 
-	var panUp = function () {
+		function dollyOut( dollyScale ) {
 
-		var v = new THREE.Vector3();
+			if ( scope.object.isPerspectiveCamera ) {
 
-		return function panUp( distance, objectMatrix ) {
+				scale /= dollyScale;
 
-			if ( scope.screenSpacePanning === true ) {
+			} else if ( scope.object.isOrthographicCamera ) {
 
-				v.setFromMatrixColumn( objectMatrix, 1 );
+				scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
+				scope.object.updateProjectionMatrix();
+				zoomChanged = true;
 
 			} else {
 
-				v.setFromMatrixColumn( objectMatrix, 0 );
-				v.crossVectors( scope.object.up, v );
+				console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
+				scope.enableZoom = false;
 
 			}
 
-			v.multiplyScalar( distance );
-
-			panOffset.add( v );
-
-		};
-
-	}();
-
-	// deltaX and deltaY are in pixels; right and down are positive
-	var pan = function () {
-
-		var offset = new THREE.Vector3();
-
-		return function pan( deltaX, deltaY ) {
+		}
 
-			var element = scope.domElement;
+		function dollyIn( dollyScale ) {
 
 			if ( scope.object.isPerspectiveCamera ) {
 
-				// perspective
-				var position = scope.object.position;
-				offset.copy( position ).sub( scope.target );
-				var targetDistance = offset.length();
-
-				// half of the fov is center to top of screen
-				targetDistance *= Math.tan( ( scope.object.fov / 2 ) * Math.PI / 180.0 );
-
-				// we use only clientHeight here so aspect ratio does not distort speed
-				panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
-				panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
+				scale *= dollyScale;
 
 			} else if ( scope.object.isOrthographicCamera ) {
 
-				// orthographic
-				panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
-				panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
+				scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
+				scope.object.updateProjectionMatrix();
+				zoomChanged = true;
 
 			} else {
 
-				// camera neither orthographic nor perspective
-				console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - pan disabled.' );
-				scope.enablePan = false;
+				console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
+				scope.enableZoom = false;
 
 			}
 
-		};
-
-	}();
+		} //
+		// event callbacks - update the object state
+		//
 
-	function dollyOut( dollyScale ) {
 
-		if ( scope.object.isPerspectiveCamera ) {
-
-			scale /= dollyScale;
-
-		} else if ( scope.object.isOrthographicCamera ) {
-
-			scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
-			scope.object.updateProjectionMatrix();
-			zoomChanged = true;
+		function handleMouseDownRotate( event ) {
 
-		} else {
-
-			console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
-			scope.enableZoom = false;
+			rotateStart.set( event.clientX, event.clientY );
 
 		}
 
-	}
-
-	function dollyIn( dollyScale ) {
-
-		if ( scope.object.isPerspectiveCamera ) {
-
-			scale *= dollyScale;
-
-		} else if ( scope.object.isOrthographicCamera ) {
+		function handleMouseDownDolly( event ) {
 
-			scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
-			scope.object.updateProjectionMatrix();
-			zoomChanged = true;
-
-		} else {
-
-			console.warn( 'WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled.' );
-			scope.enableZoom = false;
+			dollyStart.set( event.clientX, event.clientY );
 
 		}
 
-	}
-
-	//
-	// event callbacks - update the object state
-	//
-
-	function handleMouseDownRotate( event ) {
-
-		rotateStart.set( event.clientX, event.clientY );
-
-	}
-
-	function handleMouseDownDolly( event ) {
-
-		dollyStart.set( event.clientX, event.clientY );
-
-	}
-
-	function handleMouseDownPan( event ) {
-
-		panStart.set( event.clientX, event.clientY );
+		function handleMouseDownPan( event ) {
 
-	}
-
-	function handleMouseMoveRotate( event ) {
-
-		rotateEnd.set( event.clientX, event.clientY );
-
-		rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
-
-		var element = scope.domElement;
-
-		rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
-
-		rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
-
-		rotateStart.copy( rotateEnd );
-
-		scope.update();
-
-	}
-
-	function handleMouseMoveDolly( event ) {
-
-		dollyEnd.set( event.clientX, event.clientY );
-
-		dollyDelta.subVectors( dollyEnd, dollyStart );
-
-		if ( dollyDelta.y > 0 ) {
-
-			dollyOut( getZoomScale() );
-
-		} else if ( dollyDelta.y < 0 ) {
-
-			dollyIn( getZoomScale() );
+			panStart.set( event.clientX, event.clientY );
 
 		}
 
-		dollyStart.copy( dollyEnd );
-
-		scope.update();
-
-	}
+		function handleMouseMoveRotate( event ) {
 
-	function handleMouseMovePan( event ) {
-
-		panEnd.set( event.clientX, event.clientY );
-
-		panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
-
-		pan( panDelta.x, panDelta.y );
-
-		panStart.copy( panEnd );
-
-		scope.update();
-
-	}
-
-	function handleMouseUp( /*event*/ ) {
-
-		// no-op
-
-	}
-
-	function handleMouseWheel( event ) {
-
-		if ( event.deltaY < 0 ) {
-
-			dollyIn( getZoomScale() );
-
-		} else if ( event.deltaY > 0 ) {
+			rotateEnd.set( event.clientX, event.clientY );
+			rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
+			var element = scope.domElement;
+			rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
 
-			dollyOut( getZoomScale() );
+			rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
+			rotateStart.copy( rotateEnd );
+			scope.update();
 
 		}
 
-		scope.update();
+		function handleMouseMoveDolly( event ) {
 
-	}
+			dollyEnd.set( event.clientX, event.clientY );
+			dollyDelta.subVectors( dollyEnd, dollyStart );
 
-	function handleKeyDown( event ) {
+			if ( dollyDelta.y > 0 ) {
 
-		var needsUpdate = false;
+				dollyOut( getZoomScale() );
 
-		switch ( event.code ) {
+			} else if ( dollyDelta.y < 0 ) {
 
-			case scope.keys.UP:
-				pan( 0, scope.keyPanSpeed );
-				needsUpdate = true;
-				break;
+				dollyIn( getZoomScale() );
 
-			case scope.keys.BOTTOM:
-				pan( 0, - scope.keyPanSpeed );
-				needsUpdate = true;
-				break;
-
-			case scope.keys.LEFT:
-				pan( scope.keyPanSpeed, 0 );
-				needsUpdate = true;
-				break;
+			}
 
-			case scope.keys.RIGHT:
-				pan( - scope.keyPanSpeed, 0 );
-				needsUpdate = true;
-				break;
+			dollyStart.copy( dollyEnd );
+			scope.update();
 
 		}
 
-		if ( needsUpdate ) {
-
-			// prevent the browser from scrolling on cursor keys
-			event.preventDefault();
+		function handleMouseMovePan( event ) {
 
+			panEnd.set( event.clientX, event.clientY );
+			panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
+			pan( panDelta.x, panDelta.y );
+			panStart.copy( panEnd );
 			scope.update();
 
 		}
 
-
-	}
-
-	function handleTouchStartRotate( event ) {
-
-		if ( event.touches.length == 1 ) {
-
-			rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
-
-		} else {
-
-			var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
-			var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
-
-			rotateStart.set( x, y );
-
+		function handleMouseUp( ) { // no-op
 		}
 
-	}
+		function handleMouseWheel( event ) {
 
-	function handleTouchStartPan( event ) {
+			if ( event.deltaY < 0 ) {
 
-		if ( event.touches.length == 1 ) {
+				dollyIn( getZoomScale() );
 
-			panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+			} else if ( event.deltaY > 0 ) {
 
-		} else {
+				dollyOut( getZoomScale() );
 
-			var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
-			var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
+			}
 
-			panStart.set( x, y );
+			scope.update();
 
 		}
 
-	}
-
-	function handleTouchStartDolly( event ) {
-
-		var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
-		var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
-
-		var distance = Math.sqrt( dx * dx + dy * dy );
-
-		dollyStart.set( 0, distance );
-
-	}
-
-	function handleTouchStartDollyPan( event ) {
-
-		if ( scope.enableZoom ) handleTouchStartDolly( event );
+		function handleKeyDown( event ) {
 
-		if ( scope.enablePan ) handleTouchStartPan( event );
+			var needsUpdate = false;
 
-	}
+			switch ( event.code ) {
 
-	function handleTouchStartDollyRotate( event ) {
+				case scope.keys.UP:
+					pan( 0, scope.keyPanSpeed );
+					needsUpdate = true;
+					break;
 
-		if ( scope.enableZoom ) handleTouchStartDolly( event );
+				case scope.keys.BOTTOM:
+					pan( 0, - scope.keyPanSpeed );
+					needsUpdate = true;
+					break;
 
-		if ( scope.enableRotate ) handleTouchStartRotate( event );
+				case scope.keys.LEFT:
+					pan( scope.keyPanSpeed, 0 );
+					needsUpdate = true;
+					break;
 
-	}
+				case scope.keys.RIGHT:
+					pan( - scope.keyPanSpeed, 0 );
+					needsUpdate = true;
+					break;
 
-	function handleTouchMoveRotate( event ) {
-
-		if ( event.touches.length == 1 ) {
-
-			rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+			}
 
-		} else {
+			if ( needsUpdate ) {
 
-			var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
-			var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
+				// prevent the browser from scrolling on cursor keys
+				event.preventDefault();
+				scope.update();
 
-			rotateEnd.set( x, y );
+			}
 
 		}
 
-		rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
-
-		var element = scope.domElement;
-
-		rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
-
-		rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
-
-		rotateStart.copy( rotateEnd );
-
-	}
+		function handleTouchStartRotate( event ) {
 
-	function handleTouchMovePan( event ) {
+			if ( event.touches.length == 1 ) {
 
-		if ( event.touches.length == 1 ) {
+				rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
 
-			panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
-
-		} else {
+			} else {
 
-			var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
-			var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
+				var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
+				var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
+				rotateStart.set( x, y );
 
-			panEnd.set( x, y );
+			}
 
 		}
 
-		panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
-
-		pan( panDelta.x, panDelta.y );
-
-		panStart.copy( panEnd );
-
-	}
-
-	function handleTouchMoveDolly( event ) {
-
-		var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
-		var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
-
-		var distance = Math.sqrt( dx * dx + dy * dy );
-
-		dollyEnd.set( 0, distance );
-
-		dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) );
-
-		dollyOut( dollyDelta.y );
-
-		dollyStart.copy( dollyEnd );
-
-	}
-
-	function handleTouchMoveDollyPan( event ) {
-
-		if ( scope.enableZoom ) handleTouchMoveDolly( event );
-
-		if ( scope.enablePan ) handleTouchMovePan( event );
-
-	}
-
-	function handleTouchMoveDollyRotate( event ) {
-
-		if ( scope.enableZoom ) handleTouchMoveDolly( event );
-
-		if ( scope.enableRotate ) handleTouchMoveRotate( event );
-
-	}
-
-	function handleTouchEnd( /*event*/ ) {
-
-		// no-op
-
-	}
-
-	//
-	// event handlers - FSM: listen for events and reset state
-	//
+		function handleTouchStartPan( event ) {
 
-	function onPointerDown( event ) {
+			if ( event.touches.length == 1 ) {
 
-		if ( scope.enabled === false ) return;
+				panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
 
-		switch ( event.pointerType ) {
+			} else {
 
-			case 'mouse':
-			case 'pen':
-				onMouseDown( event );
-				break;
+				var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
+				var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
+				panStart.set( x, y );
 
-			// TODO touch
+			}
 
 		}
 
-	}
-
-	function onPointerMove( event ) {
-
-		if ( scope.enabled === false ) return;
+		function handleTouchStartDolly( event ) {
 
-		switch ( event.pointerType ) {
-
-			case 'mouse':
-			case 'pen':
-				onMouseMove( event );
-				break;
-
-			// TODO touch
+			var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+			var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+			var distance = Math.sqrt( dx * dx + dy * dy );
+			dollyStart.set( 0, distance );
 
 		}
 
-	}
-
-	function onPointerUp( event ) {
-
-		switch ( event.pointerType ) {
+		function handleTouchStartDollyPan( event ) {
 
-			case 'mouse':
-			case 'pen':
-				onMouseUp( event );
-				break;
-
-			// TODO touch
+			if ( scope.enableZoom ) handleTouchStartDolly( event );
+			if ( scope.enablePan ) handleTouchStartPan( event );
 
 		}
 
-	}
-
-	function onMouseDown( event ) {
-
-		// Prevent the browser from scrolling.
-		event.preventDefault();
-
-		// Manually set the focus since calling preventDefault above
-		// prevents the browser from setting it automatically.
-
-		scope.domElement.focus ? scope.domElement.focus() : window.focus();
-
-		var mouseAction;
-
-		switch ( event.button ) {
-
-			case 0:
+		function handleTouchStartDollyRotate( event ) {
 
-				mouseAction = scope.mouseButtons.LEFT;
-				break;
-
-			case 1:
-
-				mouseAction = scope.mouseButtons.MIDDLE;
-				break;
-
-			case 2:
-
-				mouseAction = scope.mouseButtons.RIGHT;
-				break;
-
-			default:
-
-				mouseAction = - 1;
+			if ( scope.enableZoom ) handleTouchStartDolly( event );
+			if ( scope.enableRotate ) handleTouchStartRotate( event );
 
 		}
 
-		switch ( mouseAction ) {
-
-			case THREE.MOUSE.DOLLY:
-
-				if ( scope.enableZoom === false ) return;
-
-				handleMouseDownDolly( event );
+		function handleTouchMoveRotate( event ) {
 
-				state = STATE.DOLLY;
+			if ( event.touches.length == 1 ) {
 
-				break;
+				rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
 
-			case THREE.MOUSE.ROTATE:
-
-				if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
-
-					if ( scope.enablePan === false ) return;
-
-					handleMouseDownPan( event );
-
-					state = STATE.PAN;
-
-				} else {
-
-					if ( scope.enableRotate === false ) return;
-
-					handleMouseDownRotate( event );
+			} else {
 
-					state = STATE.ROTATE;
+				var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
+				var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
+				rotateEnd.set( x, y );
 
-				}
-
-				break;
+			}
 
-			case THREE.MOUSE.PAN:
+			rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
+			var element = scope.domElement;
+			rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
 
-				if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
+			rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
+			rotateStart.copy( rotateEnd );
 
-					if ( scope.enableRotate === false ) return;
+		}
 
-					handleMouseDownRotate( event );
+		function handleTouchMovePan( event ) {
 
-					state = STATE.ROTATE;
+			if ( event.touches.length == 1 ) {
 
-				} else {
+				panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
 
-					if ( scope.enablePan === false ) return;
+			} else {
 
-					handleMouseDownPan( event );
+				var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
+				var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
+				panEnd.set( x, y );
 
-					state = STATE.PAN;
+			}
 
-				}
+			panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
+			pan( panDelta.x, panDelta.y );
+			panStart.copy( panEnd );
 
-				break;
+		}
 
-			default:
+		function handleTouchMoveDolly( event ) {
 
-				state = STATE.NONE;
+			var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+			var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+			var distance = Math.sqrt( dx * dx + dy * dy );
+			dollyEnd.set( 0, distance );
+			dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) );
+			dollyOut( dollyDelta.y );
+			dollyStart.copy( dollyEnd );
 
 		}
 
-		if ( state !== STATE.NONE ) {
+		function handleTouchMoveDollyPan( event ) {
 
-			scope.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove );
-			scope.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp );
-
-			scope.dispatchEvent( startEvent );
+			if ( scope.enableZoom ) handleTouchMoveDolly( event );
+			if ( scope.enablePan ) handleTouchMovePan( event );
 
 		}
 
-	}
-
-	function onMouseMove( event ) {
+		function handleTouchMoveDollyRotate( event ) {
 
-		if ( scope.enabled === false ) return;
+			if ( scope.enableZoom ) handleTouchMoveDolly( event );
+			if ( scope.enableRotate ) handleTouchMoveRotate( event );
 
-		event.preventDefault();
+		}
 
-		switch ( state ) {
+		function handleTouchEnd( ) { // no-op
+		} //
+		// event handlers - FSM: listen for events and reset state
+		//
 
-			case STATE.ROTATE:
 
-				if ( scope.enableRotate === false ) return;
+		function onPointerDown( event ) {
 
-				handleMouseMoveRotate( event );
+			if ( scope.enabled === false ) return;
 
-				break;
+			switch ( event.pointerType ) {
 
-			case STATE.DOLLY:
+				case 'mouse':
+				case 'pen':
+					onMouseDown( event );
+					break;
+			// TODO touch
 
-				if ( scope.enableZoom === false ) return;
+			}
 
-				handleMouseMoveDolly( event );
+		}
 
-				break;
+		function onPointerMove( event ) {
 
-			case STATE.PAN:
+			if ( scope.enabled === false ) return;
 
-				if ( scope.enablePan === false ) return;
+			switch ( event.pointerType ) {
 
-				handleMouseMovePan( event );
+				case 'mouse':
+				case 'pen':
+					onMouseMove( event );
+					break;
+			// TODO touch
 
-				break;
+			}
 
 		}
 
-	}
-
-	function onMouseUp( event ) {
+		function onPointerUp( event ) {
 
-		scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
-		scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
+			switch ( event.pointerType ) {
 
-		if ( scope.enabled === false ) return;
-
-		handleMouseUp( event );
-
-		scope.dispatchEvent( endEvent );
+				case 'mouse':
+				case 'pen':
+					onMouseUp( event );
+					break;
+			// TODO touch
 
-		state = STATE.NONE;
+			}
 
-	}
+		}
 
-	function onMouseWheel( event ) {
+		function onMouseDown( event ) {
 
-		if ( scope.enabled === false || scope.enableZoom === false || ( state !== STATE.NONE && state !== STATE.ROTATE ) ) return;
+			// Prevent the browser from scrolling.
+			event.preventDefault(); // Manually set the focus since calling preventDefault above
+			// prevents the browser from setting it automatically.
 
-		event.preventDefault();
+			scope.domElement.focus ? scope.domElement.focus() : window.focus();
+			var mouseAction;
 
-		scope.dispatchEvent( startEvent );
+			switch ( event.button ) {
 
-		handleMouseWheel( event );
+				case 0:
+					mouseAction = scope.mouseButtons.LEFT;
+					break;
 
-		scope.dispatchEvent( endEvent );
+				case 1:
+					mouseAction = scope.mouseButtons.MIDDLE;
+					break;
 
-	}
+				case 2:
+					mouseAction = scope.mouseButtons.RIGHT;
+					break;
 
-	function onKeyDown( event ) {
+				default:
+					mouseAction = - 1;
 
-		if ( scope.enabled === false || scope.enablePan === false ) return;
+			}
 
-		handleKeyDown( event );
+			switch ( mouseAction ) {
 
-	}
+				case THREE.MOUSE.DOLLY:
+					if ( scope.enableZoom === false ) return;
+					handleMouseDownDolly( event );
+					state = STATE.DOLLY;
+					break;
 
-	function onTouchStart( event ) {
+				case THREE.MOUSE.ROTATE:
+					if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
 
-		if ( scope.enabled === false ) return;
+						if ( scope.enablePan === false ) return;
+						handleMouseDownPan( event );
+						state = STATE.PAN;
 
-		event.preventDefault(); // prevent scrolling
+					} else {
 
-		switch ( event.touches.length ) {
+						if ( scope.enableRotate === false ) return;
+						handleMouseDownRotate( event );
+						state = STATE.ROTATE;
 
-			case 1:
+					}
 
-				switch ( scope.touches.ONE ) {
+					break;
 
-					case THREE.TOUCH.ROTATE:
+				case THREE.MOUSE.PAN:
+					if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
 
 						if ( scope.enableRotate === false ) return;
+						handleMouseDownRotate( event );
+						state = STATE.ROTATE;
 
-						handleTouchStartRotate( event );
-
-						state = STATE.TOUCH_ROTATE;
-
-						break;
-
-					case THREE.TOUCH.PAN:
+					} else {
 
 						if ( scope.enablePan === false ) return;
+						handleMouseDownPan( event );
+						state = STATE.PAN;
 
-						handleTouchStartPan( event );
-
-						state = STATE.TOUCH_PAN;
-
-						break;
+					}
 
-					default:
+					break;
 
-						state = STATE.NONE;
+				default:
+					state = STATE.NONE;
 
-				}
-
-				break;
-
-			case 2:
-
-				switch ( scope.touches.TWO ) {
-
-					case THREE.TOUCH.DOLLY_PAN:
-
-						if ( scope.enableZoom === false && scope.enablePan === false ) return;
+			}
 
-						handleTouchStartDollyPan( event );
+			if ( state !== STATE.NONE ) {
 
-						state = STATE.TOUCH_DOLLY_PAN;
+				scope.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove );
+				scope.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp );
+				scope.dispatchEvent( startEvent );
 
-						break;
+			}
 
-					case THREE.TOUCH.DOLLY_ROTATE:
+		}
 
-						if ( scope.enableZoom === false && scope.enableRotate === false ) return;
+		function onMouseMove( event ) {
 
-						handleTouchStartDollyRotate( event );
+			if ( scope.enabled === false ) return;
+			event.preventDefault();
 
-						state = STATE.TOUCH_DOLLY_ROTATE;
+			switch ( state ) {
 
-						break;
+				case STATE.ROTATE:
+					if ( scope.enableRotate === false ) return;
+					handleMouseMoveRotate( event );
+					break;
 
-					default:
+				case STATE.DOLLY:
+					if ( scope.enableZoom === false ) return;
+					handleMouseMoveDolly( event );
+					break;
 
-						state = STATE.NONE;
+				case STATE.PAN:
+					if ( scope.enablePan === false ) return;
+					handleMouseMovePan( event );
+					break;
 
-				}
+			}
 
-				break;
+		}
 
-			default:
+		function onMouseUp( event ) {
 
-				state = STATE.NONE;
+			scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
+			scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
+			if ( scope.enabled === false ) return;
+			handleMouseUp( event );
+			scope.dispatchEvent( endEvent );
+			state = STATE.NONE;
 
 		}
 
-		if ( state !== STATE.NONE ) {
+		function onMouseWheel( event ) {
 
+			if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE && state !== STATE.ROTATE ) return;
+			event.preventDefault();
 			scope.dispatchEvent( startEvent );
+			handleMouseWheel( event );
+			scope.dispatchEvent( endEvent );
 
 		}
 
-	}
-
-	function onTouchMove( event ) {
-
-		if ( scope.enabled === false ) return;
-
-		event.preventDefault(); // prevent scrolling
+		function onKeyDown( event ) {
 
-		switch ( state ) {
+			if ( scope.enabled === false || scope.enablePan === false ) return;
+			handleKeyDown( event );
 
-			case STATE.TOUCH_ROTATE:
+		}
 
-				if ( scope.enableRotate === false ) return;
+		function onTouchStart( event ) {
 
-				handleTouchMoveRotate( event );
+			if ( scope.enabled === false ) return;
+			event.preventDefault(); // prevent scrolling
 
-				scope.update();
+			switch ( event.touches.length ) {
 
-				break;
+				case 1:
+					switch ( scope.touches.ONE ) {
 
-			case STATE.TOUCH_PAN:
+						case THREE.TOUCH.ROTATE:
+							if ( scope.enableRotate === false ) return;
+							handleTouchStartRotate( event );
+							state = STATE.TOUCH_ROTATE;
+							break;
 
-				if ( scope.enablePan === false ) return;
+						case THREE.TOUCH.PAN:
+							if ( scope.enablePan === false ) return;
+							handleTouchStartPan( event );
+							state = STATE.TOUCH_PAN;
+							break;
 
-				handleTouchMovePan( event );
+						default:
+							state = STATE.NONE;
 
-				scope.update();
+					}
 
-				break;
+					break;
 
-			case STATE.TOUCH_DOLLY_PAN:
+				case 2:
+					switch ( scope.touches.TWO ) {
 
-				if ( scope.enableZoom === false && scope.enablePan === false ) return;
+						case THREE.TOUCH.DOLLY_PAN:
+							if ( scope.enableZoom === false && scope.enablePan === false ) return;
+							handleTouchStartDollyPan( event );
+							state = STATE.TOUCH_DOLLY_PAN;
+							break;
 
-				handleTouchMoveDollyPan( event );
-
-				scope.update();
+						case THREE.TOUCH.DOLLY_ROTATE:
+							if ( scope.enableZoom === false && scope.enableRotate === false ) return;
+							handleTouchStartDollyRotate( event );
+							state = STATE.TOUCH_DOLLY_ROTATE;
+							break;
 
-				break;
+						default:
+							state = STATE.NONE;
 
-			case STATE.TOUCH_DOLLY_ROTATE:
+					}
 
-				if ( scope.enableZoom === false && scope.enableRotate === false ) return;
+					break;
 
-				handleTouchMoveDollyRotate( event );
+				default:
+					state = STATE.NONE;
 
-				scope.update();
+			}
 
-				break;
+			if ( state !== STATE.NONE ) {
 
-			default:
+				scope.dispatchEvent( startEvent );
 
-				state = STATE.NONE;
+			}
 
 		}
 
-	}
+		function onTouchMove( event ) {
 
-	function onTouchEnd( event ) {
+			if ( scope.enabled === false ) return;
+			event.preventDefault(); // prevent scrolling
 
-		if ( scope.enabled === false ) return;
+			switch ( state ) {
 
-		handleTouchEnd( event );
+				case STATE.TOUCH_ROTATE:
+					if ( scope.enableRotate === false ) return;
+					handleTouchMoveRotate( event );
+					scope.update();
+					break;
 
-		scope.dispatchEvent( endEvent );
+				case STATE.TOUCH_PAN:
+					if ( scope.enablePan === false ) return;
+					handleTouchMovePan( event );
+					scope.update();
+					break;
 
-		state = STATE.NONE;
+				case STATE.TOUCH_DOLLY_PAN:
+					if ( scope.enableZoom === false && scope.enablePan === false ) return;
+					handleTouchMoveDollyPan( event );
+					scope.update();
+					break;
 
-	}
+				case STATE.TOUCH_DOLLY_ROTATE:
+					if ( scope.enableZoom === false && scope.enableRotate === false ) return;
+					handleTouchMoveDollyRotate( event );
+					scope.update();
+					break;
 
-	function onContextMenu( event ) {
+				default:
+					state = STATE.NONE;
 
-		if ( scope.enabled === false ) return;
+			}
 
-		event.preventDefault();
+		}
 
-	}
+		function onTouchEnd( event ) {
 
-	//
+			if ( scope.enabled === false ) return;
+			handleTouchEnd( event );
+			scope.dispatchEvent( endEvent );
+			state = STATE.NONE;
 
-	scope.domElement.addEventListener( 'contextmenu', onContextMenu );
+		}
 
-	scope.domElement.addEventListener( 'pointerdown', onPointerDown );
-	scope.domElement.addEventListener( 'wheel', onMouseWheel );
+		function onContextMenu( event ) {
 
-	scope.domElement.addEventListener( 'touchstart', onTouchStart );
-	scope.domElement.addEventListener( 'touchend', onTouchEnd );
-	scope.domElement.addEventListener( 'touchmove', onTouchMove );
+			if ( scope.enabled === false ) return;
+			event.preventDefault();
 
-	// force an update at start
+		} //
 
-	this.update();
 
-};
+		scope.domElement.addEventListener( 'contextmenu', onContextMenu );
+		scope.domElement.addEventListener( 'pointerdown', onPointerDown );
+		scope.domElement.addEventListener( 'wheel', onMouseWheel );
+		scope.domElement.addEventListener( 'touchstart', onTouchStart );
+		scope.domElement.addEventListener( 'touchend', onTouchEnd );
+		scope.domElement.addEventListener( 'touchmove', onTouchMove ); // force an update at start
 
-THREE.OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
-THREE.OrbitControls.prototype.constructor = THREE.OrbitControls;
+		this.update();
 
+	};
 
-// This set of controls performs orbiting, dollying (zooming), and panning.
-// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
-// This is very similar to OrbitControls, another set of touch behavior
-//
-//    Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate
-//    Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
-//    Pan - left mouse, or arrow keys / touch: one-finger move
+	OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
+	OrbitControls.prototype.constructor = OrbitControls; // This set of controls performs orbiting, dollying (zooming), and panning.
+	// Unlike TrackballControls, it maintains the "up" direction object.up (+Y by default).
+	// This is very similar to OrbitControls, another set of touch behavior
+	//
+	//		Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate
+	//		Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
+	//		Pan - left mouse, or arrow keys / touch: one-finger move
 
-THREE.MapControls = function ( object, domElement ) {
+	var MapControls = function ( object, domElement ) {
 
-	THREE.OrbitControls.call( this, object, domElement );
+		OrbitControls.call( this, object, domElement );
+		this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up
 
-	this.screenSpacePanning = false; // pan orthogonal to world-space direction camera.up
+		this.mouseButtons.LEFT = THREE.MOUSE.PAN;
+		this.mouseButtons.RIGHT = THREE.MOUSE.ROTATE;
+		this.touches.ONE = THREE.TOUCH.PAN;
+		this.touches.TWO = THREE.TOUCH.DOLLY_ROTATE;
 
-	this.mouseButtons.LEFT = THREE.MOUSE.PAN;
-	this.mouseButtons.RIGHT = THREE.MOUSE.ROTATE;
+	};
 
-	this.touches.ONE = THREE.TOUCH.PAN;
-	this.touches.TWO = THREE.TOUCH.DOLLY_ROTATE;
+	MapControls.prototype = Object.create( THREE.EventDispatcher.prototype );
+	MapControls.prototype.constructor = MapControls;
 
-};
+	THREE.MapControls = MapControls;
+	THREE.OrbitControls = OrbitControls;
 
-THREE.MapControls.prototype = Object.create( THREE.EventDispatcher.prototype );
-THREE.MapControls.prototype.constructor = THREE.MapControls;
+} )();

+ 95 - 100
examples/js/controls/PointerLockControls.js

@@ -1,156 +1,151 @@
-THREE.PointerLockControls = function ( camera, domElement ) {
+( function () {
 
-	if ( domElement === undefined ) {
+	var PointerLockControls = function ( camera, domElement ) {
 
-		console.warn( 'THREE.PointerLockControls: The second parameter "domElement" is now mandatory.' );
-		domElement = document.body;
+		if ( domElement === undefined ) {
 
-	}
+			console.warn( 'THREE.PointerLockControls: The second parameter "domElement" is now mandatory.' );
+			domElement = document.body;
 
-	this.domElement = domElement;
-	this.isLocked = false;
-
-	// Set to constrain the pitch of the camera
-	// Range is 0 to Math.PI radians
-	this.minPolarAngle = 0; // radians
-	this.maxPolarAngle = Math.PI; // radians
-
-	//
-	// internals
-	//
-
-	var scope = this;
-
-	var changeEvent = { type: 'change' };
-	var lockEvent = { type: 'lock' };
-	var unlockEvent = { type: 'unlock' };
-
-	var euler = new THREE.Euler( 0, 0, 0, 'YXZ' );
-
-	var PI_2 = Math.PI / 2;
-
-	var vec = new THREE.Vector3();
-
-	function onMouseMove( event ) {
-
-		if ( scope.isLocked === false ) return;
+		}
 
-		var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
-		var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
+		this.domElement = domElement;
+		this.isLocked = false; // Set to constrain the pitch of the camera
+		// Range is 0 to Math.PI radians
 
-		euler.setFromQuaternion( camera.quaternion );
+		this.minPolarAngle = 0; // radians
 
-		euler.y -= movementX * 0.002;
-		euler.x -= movementY * 0.002;
+		this.maxPolarAngle = Math.PI; // radians
+		//
+		// internals
+		//
 
-		euler.x = Math.max( PI_2 - scope.maxPolarAngle, Math.min( PI_2 - scope.minPolarAngle, euler.x ) );
+		var scope = this;
+		var changeEvent = {
+			type: 'change'
+		};
+		var lockEvent = {
+			type: 'lock'
+		};
+		var unlockEvent = {
+			type: 'unlock'
+		};
+		var euler = new THREE.Euler( 0, 0, 0, 'YXZ' );
+		var PI_2 = Math.PI / 2;
+		var vec = new THREE.Vector3();
+
+		function onMouseMove( event ) {
+
+			if ( scope.isLocked === false ) return;
+			var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
+			var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
+			euler.setFromQuaternion( camera.quaternion );
+			euler.y -= movementX * 0.002;
+			euler.x -= movementY * 0.002;
+			euler.x = Math.max( PI_2 - scope.maxPolarAngle, Math.min( PI_2 - scope.minPolarAngle, euler.x ) );
+			camera.quaternion.setFromEuler( euler );
+			scope.dispatchEvent( changeEvent );
 
-		camera.quaternion.setFromEuler( euler );
+		}
 
-		scope.dispatchEvent( changeEvent );
+		function onPointerlockChange() {
 
-	}
+			if ( scope.domElement.ownerDocument.pointerLockElement === scope.domElement ) {
 
-	function onPointerlockChange() {
+				scope.dispatchEvent( lockEvent );
+				scope.isLocked = true;
 
-		if ( scope.domElement.ownerDocument.pointerLockElement === scope.domElement ) {
+			} else {
 
-			scope.dispatchEvent( lockEvent );
+				scope.dispatchEvent( unlockEvent );
+				scope.isLocked = false;
 
-			scope.isLocked = true;
+			}
 
-		} else {
+		}
 
-			scope.dispatchEvent( unlockEvent );
+		function onPointerlockError() {
 
-			scope.isLocked = false;
+			console.error( 'THREE.PointerLockControls: Unable to use Pointer Lock API' );
 
 		}
 
-	}
-
-	function onPointerlockError() {
+		this.connect = function () {
 
-		console.error( 'THREE.PointerLockControls: Unable to use Pointer Lock API' );
+			scope.domElement.ownerDocument.addEventListener( 'mousemove', onMouseMove );
+			scope.domElement.ownerDocument.addEventListener( 'pointerlockchange', onPointerlockChange );
+			scope.domElement.ownerDocument.addEventListener( 'pointerlockerror', onPointerlockError );
 
-	}
+		};
 
-	this.connect = function () {
+		this.disconnect = function () {
 
-		scope.domElement.ownerDocument.addEventListener( 'mousemove', onMouseMove );
-		scope.domElement.ownerDocument.addEventListener( 'pointerlockchange', onPointerlockChange );
-		scope.domElement.ownerDocument.addEventListener( 'pointerlockerror', onPointerlockError );
+			scope.domElement.ownerDocument.removeEventListener( 'mousemove', onMouseMove );
+			scope.domElement.ownerDocument.removeEventListener( 'pointerlockchange', onPointerlockChange );
+			scope.domElement.ownerDocument.removeEventListener( 'pointerlockerror', onPointerlockError );
 
-	};
+		};
 
-	this.disconnect = function () {
+		this.dispose = function () {
 
-		scope.domElement.ownerDocument.removeEventListener( 'mousemove', onMouseMove );
-		scope.domElement.ownerDocument.removeEventListener( 'pointerlockchange', onPointerlockChange );
-		scope.domElement.ownerDocument.removeEventListener( 'pointerlockerror', onPointerlockError );
+			this.disconnect();
 
-	};
+		};
 
-	this.dispose = function () {
+		this.getObject = function () {
 
-		this.disconnect();
+			// retaining this method for backward compatibility
+			return camera;
 
-	};
+		};
 
-	this.getObject = function () { // retaining this method for backward compatibility
+		this.getDirection = function () {
 
-		return camera;
+			var direction = new THREE.Vector3( 0, 0, - 1 );
+			return function ( v ) {
 
-	};
+				return v.copy( direction ).applyQuaternion( camera.quaternion );
 
-	this.getDirection = function () {
+			};
 
-		var direction = new THREE.Vector3( 0, 0, - 1 );
+		}();
 
-		return function ( v ) {
+		this.moveForward = function ( distance ) {
 
-			return v.copy( direction ).applyQuaternion( camera.quaternion );
+			// move forward parallel to the xz-plane
+			// assumes camera.up is y-up
+			vec.setFromMatrixColumn( camera.matrix, 0 );
+			vec.crossVectors( camera.up, vec );
+			camera.position.addScaledVector( vec, distance );
 
 		};
 
-	}();
-
-	this.moveForward = function ( distance ) {
-
-		// move forward parallel to the xz-plane
-		// assumes camera.up is y-up
-
-		vec.setFromMatrixColumn( camera.matrix, 0 );
+		this.moveRight = function ( distance ) {
 
-		vec.crossVectors( camera.up, vec );
+			vec.setFromMatrixColumn( camera.matrix, 0 );
+			camera.position.addScaledVector( vec, distance );
 
-		camera.position.addScaledVector( vec, distance );
-
-	};
-
-	this.moveRight = function ( distance ) {
+		};
 
-		vec.setFromMatrixColumn( camera.matrix, 0 );
+		this.lock = function () {
 
-		camera.position.addScaledVector( vec, distance );
+			this.domElement.requestPointerLock();
 
-	};
-
-	this.lock = function () {
+		};
 
-		this.domElement.requestPointerLock();
+		this.unlock = function () {
 
-	};
+			scope.domElement.ownerDocument.exitPointerLock();
 
-	this.unlock = function () {
+		};
 
-		scope.domElement.ownerDocument.exitPointerLock();
+		this.connect();
 
 	};
 
-	this.connect();
+	PointerLockControls.prototype = Object.create( THREE.EventDispatcher.prototype );
+	PointerLockControls.prototype.constructor = PointerLockControls;
 
-};
+	THREE.PointerLockControls = PointerLockControls;
 
-THREE.PointerLockControls.prototype = Object.create( THREE.EventDispatcher.prototype );
-THREE.PointerLockControls.prototype.constructor = THREE.PointerLockControls;
+} )();

+ 446 - 469
examples/js/controls/TrackballControls.js

@@ -1,214 +1,187 @@
-THREE.TrackballControls = function ( object, domElement ) {
-
-	if ( domElement === undefined ) console.warn( 'THREE.TrackballControls: The second parameter "domElement" is now mandatory.' );
-	if ( domElement === document ) console.error( 'THREE.TrackballControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' );
-
-	var scope = this;
-	var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 };
-
-	this.object = object;
-	this.domElement = domElement;
-
-	// API
-
-	this.enabled = true;
-
-	this.screen = { left: 0, top: 0, width: 0, height: 0 };
-
-	this.rotateSpeed = 1.0;
-	this.zoomSpeed = 1.2;
-	this.panSpeed = 0.3;
-
-	this.noRotate = false;
-	this.noZoom = false;
-	this.noPan = false;
-
-	this.staticMoving = false;
-	this.dynamicDampingFactor = 0.2;
-
-	this.minDistance = 0;
-	this.maxDistance = Infinity;
-
-	this.keys = [ 'KeyA' /*A*/, 'KeyS' /*S*/, 'KeyD' /*D*/ ];
-
-	this.mouseButtons = { LEFT: THREE.MOUSE.ROTATE, MIDDLE: THREE.MOUSE.DOLLY, RIGHT: THREE.MOUSE.PAN };
-
-	// internals
-
-	this.target = new THREE.Vector3();
-
-	var EPS = 0.000001;
-
-	var lastPosition = new THREE.Vector3();
-	var lastZoom = 1;
-
-	var _state = STATE.NONE,
-		_keyState = STATE.NONE,
-
-		_eye = new THREE.Vector3(),
-
-		_movePrev = new THREE.Vector2(),
-		_moveCurr = new THREE.Vector2(),
-
-		_lastAxis = new THREE.Vector3(),
-		_lastAngle = 0,
-
-		_zoomStart = new THREE.Vector2(),
-		_zoomEnd = new THREE.Vector2(),
-
-		_touchZoomDistanceStart = 0,
-		_touchZoomDistanceEnd = 0,
-
-		_panStart = new THREE.Vector2(),
-		_panEnd = new THREE.Vector2();
-
-	// for reset
-
-	this.target0 = this.target.clone();
-	this.position0 = this.object.position.clone();
-	this.up0 = this.object.up.clone();
-	this.zoom0 = this.object.zoom;
-
-	// events
-
-	var changeEvent = { type: 'change' };
-	var startEvent = { type: 'start' };
-	var endEvent = { type: 'end' };
-
-
-	// methods
-
-	this.handleResize = function () {
-
-		var box = scope.domElement.getBoundingClientRect();
-		// adjustments come from similar code in the jquery offset() function
-		var d = scope.domElement.ownerDocument.documentElement;
-		scope.screen.left = box.left + window.pageXOffset - d.clientLeft;
-		scope.screen.top = box.top + window.pageYOffset - d.clientTop;
-		scope.screen.width = box.width;
-		scope.screen.height = box.height;
-
-	};
-
-	var getMouseOnScreen = ( function () {
-
-		var vector = new THREE.Vector2();
-
-		return function getMouseOnScreen( pageX, pageY ) {
-
-			vector.set(
-				( pageX - scope.screen.left ) / scope.screen.width,
-				( pageY - scope.screen.top ) / scope.screen.height
-			);
-
-			return vector;
-
+( function () {
+
+	var TrackballControls = function ( object, domElement ) {
+
+		if ( domElement === undefined ) console.warn( 'THREE.TrackballControls: The second parameter "domElement" is now mandatory.' );
+		if ( domElement === document ) console.error( 'THREE.TrackballControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' );
+		var scope = this;
+		var STATE = {
+			NONE: - 1,
+			ROTATE: 0,
+			ZOOM: 1,
+			PAN: 2,
+			TOUCH_ROTATE: 3,
+			TOUCH_ZOOM_PAN: 4
 		};
+		this.object = object;
+		this.domElement = domElement; // API
+
+		this.enabled = true;
+		this.screen = {
+			left: 0,
+			top: 0,
+			width: 0,
+			height: 0
+		};
+		this.rotateSpeed = 1.0;
+		this.zoomSpeed = 1.2;
+		this.panSpeed = 0.3;
+		this.noRotate = false;
+		this.noZoom = false;
+		this.noPan = false;
+		this.staticMoving = false;
+		this.dynamicDampingFactor = 0.2;
+		this.minDistance = 0;
+		this.maxDistance = Infinity;
+		this.keys = [ 'KeyA',
+			/*A*/
+	 'KeyS',
+			/*S*/
+	 'KeyD'
+			/*D*/
+		];
+		this.mouseButtons = {
+			LEFT: THREE.MOUSE.ROTATE,
+			MIDDLE: THREE.MOUSE.DOLLY,
+			RIGHT: THREE.MOUSE.PAN
+		}; // internals
+
+		this.target = new THREE.Vector3();
+		var EPS = 0.000001;
+		var lastPosition = new THREE.Vector3();
+		var lastZoom = 1;
+
+		var _state = STATE.NONE,
+			_keyState = STATE.NONE,
+			_eye = new THREE.Vector3(),
+			_movePrev = new THREE.Vector2(),
+			_moveCurr = new THREE.Vector2(),
+			_lastAxis = new THREE.Vector3(),
+			_lastAngle = 0,
+			_zoomStart = new THREE.Vector2(),
+			_zoomEnd = new THREE.Vector2(),
+			_touchZoomDistanceStart = 0,
+			_touchZoomDistanceEnd = 0,
+			_panStart = new THREE.Vector2(),
+			_panEnd = new THREE.Vector2(); // for reset
+
+
+		this.target0 = this.target.clone();
+		this.position0 = this.object.position.clone();
+		this.up0 = this.object.up.clone();
+		this.zoom0 = this.object.zoom; // events
+
+		var changeEvent = {
+			type: 'change'
+		};
+		var startEvent = {
+			type: 'start'
+		};
+		var endEvent = {
+			type: 'end'
+		}; // methods
 
-	}() );
-
-	var getMouseOnCircle = ( function () {
-
-		var vector = new THREE.Vector2();
-
-		return function getMouseOnCircle( pageX, pageY ) {
+		this.handleResize = function () {
 
-			vector.set(
-				( ( pageX - scope.screen.width * 0.5 - scope.screen.left ) / ( scope.screen.width * 0.5 ) ),
-				( ( scope.screen.height + 2 * ( scope.screen.top - pageY ) ) / scope.screen.width ) // screen.width intentional
-			);
+			var box = scope.domElement.getBoundingClientRect(); // adjustments come from similar code in the jquery offset() function
 
-			return vector;
+			var d = scope.domElement.ownerDocument.documentElement;
+			scope.screen.left = box.left + window.pageXOffset - d.clientLeft;
+			scope.screen.top = box.top + window.pageYOffset - d.clientTop;
+			scope.screen.width = box.width;
+			scope.screen.height = box.height;
 
 		};
 
-	}() );
-
-	this.rotateCamera = ( function () {
+		var getMouseOnScreen = function () {
 
-		var axis = new THREE.Vector3(),
-			quaternion = new THREE.Quaternion(),
-			eyeDirection = new THREE.Vector3(),
-			objectUpDirection = new THREE.Vector3(),
-			objectSidewaysDirection = new THREE.Vector3(),
-			moveDirection = new THREE.Vector3(),
-			angle;
+			var vector = new THREE.Vector2();
+			return function getMouseOnScreen( pageX, pageY ) {
 
-		return function rotateCamera() {
+				vector.set( ( pageX - scope.screen.left ) / scope.screen.width, ( pageY - scope.screen.top ) / scope.screen.height );
+				return vector;
 
-			moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 );
-			angle = moveDirection.length();
+			};
 
-			if ( angle ) {
+		}();
 
-				_eye.copy( scope.object.position ).sub( scope.target );
+		var getMouseOnCircle = function () {
 
-				eyeDirection.copy( _eye ).normalize();
-				objectUpDirection.copy( scope.object.up ).normalize();
-				objectSidewaysDirection.crossVectors( objectUpDirection, eyeDirection ).normalize();
+			var vector = new THREE.Vector2();
+			return function getMouseOnCircle( pageX, pageY ) {
 
-				objectUpDirection.setLength( _moveCurr.y - _movePrev.y );
-				objectSidewaysDirection.setLength( _moveCurr.x - _movePrev.x );
+				vector.set( ( pageX - scope.screen.width * 0.5 - scope.screen.left ) / ( scope.screen.width * 0.5 ), ( scope.screen.height + 2 * ( scope.screen.top - pageY ) ) / scope.screen.width // screen.width intentional
+				);
+				return vector;
 
-				moveDirection.copy( objectUpDirection.add( objectSidewaysDirection ) );
+			};
 
-				axis.crossVectors( moveDirection, _eye ).normalize();
+		}();
 
-				angle *= scope.rotateSpeed;
-				quaternion.setFromAxisAngle( axis, angle );
+		this.rotateCamera = function () {
 
-				_eye.applyQuaternion( quaternion );
-				scope.object.up.applyQuaternion( quaternion );
+			var axis = new THREE.Vector3(),
+				quaternion = new THREE.Quaternion(),
+				eyeDirection = new THREE.Vector3(),
+				objectUpDirection = new THREE.Vector3(),
+				objectSidewaysDirection = new THREE.Vector3(),
+				moveDirection = new THREE.Vector3(),
+				angle;
+			return function rotateCamera() {
 
-				_lastAxis.copy( axis );
-				_lastAngle = angle;
+				moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 );
+				angle = moveDirection.length();
 
-			} else if ( ! scope.staticMoving && _lastAngle ) {
+				if ( angle ) {
 
-				_lastAngle *= Math.sqrt( 1.0 - scope.dynamicDampingFactor );
-				_eye.copy( scope.object.position ).sub( scope.target );
-				quaternion.setFromAxisAngle( _lastAxis, _lastAngle );
-				_eye.applyQuaternion( quaternion );
-				scope.object.up.applyQuaternion( quaternion );
+					_eye.copy( scope.object.position ).sub( scope.target );
 
-			}
+					eyeDirection.copy( _eye ).normalize();
+					objectUpDirection.copy( scope.object.up ).normalize();
+					objectSidewaysDirection.crossVectors( objectUpDirection, eyeDirection ).normalize();
+					objectUpDirection.setLength( _moveCurr.y - _movePrev.y );
+					objectSidewaysDirection.setLength( _moveCurr.x - _movePrev.x );
+					moveDirection.copy( objectUpDirection.add( objectSidewaysDirection ) );
+					axis.crossVectors( moveDirection, _eye ).normalize();
+					angle *= scope.rotateSpeed;
+					quaternion.setFromAxisAngle( axis, angle );
 
-			_movePrev.copy( _moveCurr );
+					_eye.applyQuaternion( quaternion );
 
-		};
+					scope.object.up.applyQuaternion( quaternion );
 
-	}() );
+					_lastAxis.copy( axis );
 
+					_lastAngle = angle;
 
-	this.zoomCamera = function () {
+				} else if ( ! scope.staticMoving && _lastAngle ) {
 
-		var factor;
+					_lastAngle *= Math.sqrt( 1.0 - scope.dynamicDampingFactor );
 
-		if ( _state === STATE.TOUCH_ZOOM_PAN ) {
+					_eye.copy( scope.object.position ).sub( scope.target );
 
-			factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
-			_touchZoomDistanceStart = _touchZoomDistanceEnd;
+					quaternion.setFromAxisAngle( _lastAxis, _lastAngle );
 
-			if ( scope.object.isPerspectiveCamera ) {
+					_eye.applyQuaternion( quaternion );
 
-				_eye.multiplyScalar( factor );
+					scope.object.up.applyQuaternion( quaternion );
 
-			} else if ( scope.object.isOrthographicCamera ) {
+				}
 
-				scope.object.zoom *= factor;
-				scope.object.updateProjectionMatrix();
+				_movePrev.copy( _moveCurr );
 
-			} else {
+			};
 
-				console.warn( 'THREE.TrackballControls: Unsupported camera type' );
+		}();
 
-			}
+		this.zoomCamera = function () {
 
-		} else {
+			var factor;
 
-			factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * scope.zoomSpeed;
+			if ( _state === STATE.TOUCH_ZOOM_PAN ) {
 
-			if ( factor !== 1.0 && factor > 0.0 ) {
+				factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
+				_touchZoomDistanceStart = _touchZoomDistanceEnd;
 
 				if ( scope.object.isPerspectiveCamera ) {
 
@@ -216,7 +189,7 @@ THREE.TrackballControls = function ( object, domElement ) {
 
 				} else if ( scope.object.isOrthographicCamera ) {
 
-					scope.object.zoom /= factor;
+					scope.object.zoom *= factor;
 					scope.object.updateProjectionMatrix();
 
 				} else {
@@ -225,59 +198,36 @@ THREE.TrackballControls = function ( object, domElement ) {
 
 				}
 
-			}
-
-			if ( scope.staticMoving ) {
-
-				_zoomStart.copy( _zoomEnd );
-
 			} else {
 
-				_zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;
+				factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * scope.zoomSpeed;
 
-			}
+				if ( factor !== 1.0 && factor > 0.0 ) {
 
-		}
+					if ( scope.object.isPerspectiveCamera ) {
 
-	};
-
-	this.panCamera = ( function () {
-
-		var mouseChange = new THREE.Vector2(),
-			objectUp = new THREE.Vector3(),
-			pan = new THREE.Vector3();
+						_eye.multiplyScalar( factor );
 
-		return function panCamera() {
+					} else if ( scope.object.isOrthographicCamera ) {
 
-			mouseChange.copy( _panEnd ).sub( _panStart );
+						scope.object.zoom /= factor;
+						scope.object.updateProjectionMatrix();
 
-			if ( mouseChange.lengthSq() ) {
+					} else {
 
-				if ( scope.object.isOrthographicCamera ) {
+						console.warn( 'THREE.TrackballControls: Unsupported camera type' );
 
-					var scale_x = ( scope.object.right - scope.object.left ) / scope.object.zoom / scope.domElement.clientWidth;
-					var scale_y = ( scope.object.top - scope.object.bottom ) / scope.object.zoom / scope.domElement.clientWidth;
-
-					mouseChange.x *= scale_x;
-					mouseChange.y *= scale_y;
+					}
 
 				}
 
-				mouseChange.multiplyScalar( _eye.length() * scope.panSpeed );
-
-				pan.copy( _eye ).cross( scope.object.up ).setLength( mouseChange.x );
-				pan.add( objectUp.copy( scope.object.up ).setLength( mouseChange.y ) );
-
-				scope.object.position.add( pan );
-				scope.target.add( pan );
-
 				if ( scope.staticMoving ) {
 
-					_panStart.copy( _panEnd );
+					_zoomStart.copy( _zoomEnd );
 
 				} else {
 
-					_panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( scope.dynamicDampingFactor ) );
+					_zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;
 
 				}
 
@@ -285,455 +235,482 @@ THREE.TrackballControls = function ( object, domElement ) {
 
 		};
 
-	}() );
+		this.panCamera = function () {
 
-	this.checkDistances = function () {
+			var mouseChange = new THREE.Vector2(),
+				objectUp = new THREE.Vector3(),
+				pan = new THREE.Vector3();
+			return function panCamera() {
 
-		if ( ! scope.noZoom || ! scope.noPan ) {
+				mouseChange.copy( _panEnd ).sub( _panStart );
 
-			if ( _eye.lengthSq() > scope.maxDistance * scope.maxDistance ) {
+				if ( mouseChange.lengthSq() ) {
 
-				scope.object.position.addVectors( scope.target, _eye.setLength( scope.maxDistance ) );
-				_zoomStart.copy( _zoomEnd );
+					if ( scope.object.isOrthographicCamera ) {
 
-			}
+						var scale_x = ( scope.object.right - scope.object.left ) / scope.object.zoom / scope.domElement.clientWidth;
+						var scale_y = ( scope.object.top - scope.object.bottom ) / scope.object.zoom / scope.domElement.clientWidth;
+						mouseChange.x *= scale_x;
+						mouseChange.y *= scale_y;
 
-			if ( _eye.lengthSq() < scope.minDistance * scope.minDistance ) {
+					}
 
-				scope.object.position.addVectors( scope.target, _eye.setLength( scope.minDistance ) );
-				_zoomStart.copy( _zoomEnd );
+					mouseChange.multiplyScalar( _eye.length() * scope.panSpeed );
+					pan.copy( _eye ).cross( scope.object.up ).setLength( mouseChange.x );
+					pan.add( objectUp.copy( scope.object.up ).setLength( mouseChange.y ) );
+					scope.object.position.add( pan );
+					scope.target.add( pan );
 
-			}
+					if ( scope.staticMoving ) {
 
-		}
+						_panStart.copy( _panEnd );
 
-	};
+					} else {
 
-	this.update = function () {
+						_panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( scope.dynamicDampingFactor ) );
 
-		_eye.subVectors( scope.object.position, scope.target );
+					}
 
-		if ( ! scope.noRotate ) {
+				}
 
-			scope.rotateCamera();
+			};
 
-		}
+		}();
 
-		if ( ! scope.noZoom ) {
+		this.checkDistances = function () {
 
-			scope.zoomCamera();
+			if ( ! scope.noZoom || ! scope.noPan ) {
 
-		}
+				if ( _eye.lengthSq() > scope.maxDistance * scope.maxDistance ) {
 
-		if ( ! scope.noPan ) {
+					scope.object.position.addVectors( scope.target, _eye.setLength( scope.maxDistance ) );
 
-			scope.panCamera();
-
-		}
+					_zoomStart.copy( _zoomEnd );
 
-		scope.object.position.addVectors( scope.target, _eye );
-
-		if ( scope.object.isPerspectiveCamera ) {
+				}
 
-			scope.checkDistances();
+				if ( _eye.lengthSq() < scope.minDistance * scope.minDistance ) {
 
-			scope.object.lookAt( scope.target );
+					scope.object.position.addVectors( scope.target, _eye.setLength( scope.minDistance ) );
 
-			if ( lastPosition.distanceToSquared( scope.object.position ) > EPS ) {
+					_zoomStart.copy( _zoomEnd );
 
-				scope.dispatchEvent( changeEvent );
-
-				lastPosition.copy( scope.object.position );
+				}
 
 			}
 
-		} else if ( scope.object.isOrthographicCamera ) {
+		};
 
-			scope.object.lookAt( scope.target );
+		this.update = function () {
 
-			if ( lastPosition.distanceToSquared( scope.object.position ) > EPS || lastZoom !== scope.object.zoom ) {
+			_eye.subVectors( scope.object.position, scope.target );
 
-				scope.dispatchEvent( changeEvent );
+			if ( ! scope.noRotate ) {
 
-				lastPosition.copy( scope.object.position );
-				lastZoom = scope.object.zoom;
+				scope.rotateCamera();
 
 			}
 
-		} else {
+			if ( ! scope.noZoom ) {
 
-			console.warn( 'THREE.TrackballControls: Unsupported camera type' );
+				scope.zoomCamera();
 
-		}
-
-	};
-
-	this.reset = function () {
+			}
 
-		_state = STATE.NONE;
-		_keyState = STATE.NONE;
+			if ( ! scope.noPan ) {
 
-		scope.target.copy( scope.target0 );
-		scope.object.position.copy( scope.position0 );
-		scope.object.up.copy( scope.up0 );
-		scope.object.zoom = scope.zoom0;
+				scope.panCamera();
 
-		scope.object.updateProjectionMatrix();
+			}
 
-		_eye.subVectors( scope.object.position, scope.target );
+			scope.object.position.addVectors( scope.target, _eye );
 
-		scope.object.lookAt( scope.target );
+			if ( scope.object.isPerspectiveCamera ) {
 
-		scope.dispatchEvent( changeEvent );
+				scope.checkDistances();
+				scope.object.lookAt( scope.target );
 
-		lastPosition.copy( scope.object.position );
-		lastZoom = scope.object.zoom;
+				if ( lastPosition.distanceToSquared( scope.object.position ) > EPS ) {
 
-	};
+					scope.dispatchEvent( changeEvent );
+					lastPosition.copy( scope.object.position );
 
-	// listeners
+				}
 
-	function onPointerDown( event ) {
+			} else if ( scope.object.isOrthographicCamera ) {
 
-		if ( scope.enabled === false ) return;
+				scope.object.lookAt( scope.target );
 
-		switch ( event.pointerType ) {
+				if ( lastPosition.distanceToSquared( scope.object.position ) > EPS || lastZoom !== scope.object.zoom ) {
 
-			case 'mouse':
-			case 'pen':
-				onMouseDown( event );
-				break;
+					scope.dispatchEvent( changeEvent );
+					lastPosition.copy( scope.object.position );
+					lastZoom = scope.object.zoom;
 
-			// TODO touch
+				}
 
-		}
+			} else {
 
-	}
+				console.warn( 'THREE.TrackballControls: Unsupported camera type' );
 
-	function onPointerMove( event ) {
+			}
 
-		if ( scope.enabled === false ) return;
+		};
 
-		switch ( event.pointerType ) {
+		this.reset = function () {
 
-			case 'mouse':
-			case 'pen':
-				onMouseMove( event );
-				break;
+			_state = STATE.NONE;
+			_keyState = STATE.NONE;
+			scope.target.copy( scope.target0 );
+			scope.object.position.copy( scope.position0 );
+			scope.object.up.copy( scope.up0 );
+			scope.object.zoom = scope.zoom0;
+			scope.object.updateProjectionMatrix();
 
-			// TODO touch
+			_eye.subVectors( scope.object.position, scope.target );
 
-		}
+			scope.object.lookAt( scope.target );
+			scope.dispatchEvent( changeEvent );
+			lastPosition.copy( scope.object.position );
+			lastZoom = scope.object.zoom;
 
-	}
+		}; // listeners
 
-	function onPointerUp( event ) {
 
-		if ( scope.enabled === false ) return;
+		function onPointerDown( event ) {
 
-		switch ( event.pointerType ) {
+			if ( scope.enabled === false ) return;
 
-			case 'mouse':
-			case 'pen':
-				onMouseUp( event );
-				break;
+			switch ( event.pointerType ) {
 
+				case 'mouse':
+				case 'pen':
+					onMouseDown( event );
+					break;
 			// TODO touch
 
-		}
+			}
 
-	}
+		}
 
-	function keydown( event ) {
+		function onPointerMove( event ) {
 
-		if ( scope.enabled === false ) return;
+			if ( scope.enabled === false ) return;
 
-		window.removeEventListener( 'keydown', keydown );
+			switch ( event.pointerType ) {
 
-		if ( _keyState !== STATE.NONE ) {
+				case 'mouse':
+				case 'pen':
+					onMouseMove( event );
+					break;
+			// TODO touch
 
-			return;
+			}
 
-		} else if ( event.code === scope.keys[ STATE.ROTATE ] && ! scope.noRotate ) {
+		}
 
-			_keyState = STATE.ROTATE;
+		function onPointerUp( event ) {
 
-		} else if ( event.code === scope.keys[ STATE.ZOOM ] && ! scope.noZoom ) {
+			if ( scope.enabled === false ) return;
 
-			_keyState = STATE.ZOOM;
+			switch ( event.pointerType ) {
 
-		} else if ( event.code === scope.keys[ STATE.PAN ] && ! scope.noPan ) {
+				case 'mouse':
+				case 'pen':
+					onMouseUp( event );
+					break;
+			// TODO touch
 
-			_keyState = STATE.PAN;
+			}
 
 		}
 
-	}
+		function keydown( event ) {
 
-	function keyup() {
+			if ( scope.enabled === false ) return;
+			window.removeEventListener( 'keydown', keydown );
 
-		if ( scope.enabled === false ) return;
+			if ( _keyState !== STATE.NONE ) {
 
-		_keyState = STATE.NONE;
+				return;
 
-		window.addEventListener( 'keydown', keydown );
-
-	}
+			} else if ( event.code === scope.keys[ STATE.ROTATE ] && ! scope.noRotate ) {
 
-	function onMouseDown( event ) {
+				_keyState = STATE.ROTATE;
 
-		event.preventDefault();
+			} else if ( event.code === scope.keys[ STATE.ZOOM ] && ! scope.noZoom ) {
 
-		if ( _state === STATE.NONE ) {
+				_keyState = STATE.ZOOM;
 
-			switch ( event.button ) {
+			} else if ( event.code === scope.keys[ STATE.PAN ] && ! scope.noPan ) {
 
-				case scope.mouseButtons.LEFT:
-					_state = STATE.ROTATE;
-					break;
+				_keyState = STATE.PAN;
 
-				case scope.mouseButtons.MIDDLE:
-					_state = STATE.ZOOM;
-					break;
+			}
 
-				case scope.mouseButtons.RIGHT:
-					_state = STATE.PAN;
-					break;
+		}
 
-				default:
-					_state = STATE.NONE;
+		function keyup() {
 
-			}
+			if ( scope.enabled === false ) return;
+			_keyState = STATE.NONE;
+			window.addEventListener( 'keydown', keydown );
 
 		}
 
-		var state = ( _keyState !== STATE.NONE ) ? _keyState : _state;
+		function onMouseDown( event ) {
 
-		if ( state === STATE.ROTATE && ! scope.noRotate ) {
+			event.preventDefault();
 
-			_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
-			_movePrev.copy( _moveCurr );
+			if ( _state === STATE.NONE ) {
 
-		} else if ( state === STATE.ZOOM && ! scope.noZoom ) {
+				switch ( event.button ) {
 
-			_zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
-			_zoomEnd.copy( _zoomStart );
+					case scope.mouseButtons.LEFT:
+						_state = STATE.ROTATE;
+						break;
 
-		} else if ( state === STATE.PAN && ! scope.noPan ) {
+					case scope.mouseButtons.MIDDLE:
+						_state = STATE.ZOOM;
+						break;
 
-			_panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
-			_panEnd.copy( _panStart );
+					case scope.mouseButtons.RIGHT:
+						_state = STATE.PAN;
+						break;
 
-		}
+					default:
+						_state = STATE.NONE;
 
-		scope.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove );
-		scope.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp );
+				}
 
-		scope.dispatchEvent( startEvent );
+			}
 
-	}
+			var state = _keyState !== STATE.NONE ? _keyState : _state;
 
-	function onMouseMove( event ) {
+			if ( state === STATE.ROTATE && ! scope.noRotate ) {
 
-		if ( scope.enabled === false ) return;
+				_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
+
+				_movePrev.copy( _moveCurr );
 
-		event.preventDefault();
+			} else if ( state === STATE.ZOOM && ! scope.noZoom ) {
 
-		var state = ( _keyState !== STATE.NONE ) ? _keyState : _state;
+				_zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
 
-		if ( state === STATE.ROTATE && ! scope.noRotate ) {
+				_zoomEnd.copy( _zoomStart );
 
-			_movePrev.copy( _moveCurr );
-			_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
+			} else if ( state === STATE.PAN && ! scope.noPan ) {
 
-		} else if ( state === STATE.ZOOM && ! scope.noZoom ) {
+				_panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
 
-			_zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
+				_panEnd.copy( _panStart );
 
-		} else if ( state === STATE.PAN && ! scope.noPan ) {
+			}
 
-			_panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
+			scope.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove );
+			scope.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp );
+			scope.dispatchEvent( startEvent );
 
 		}
 
-	}
+		function onMouseMove( event ) {
 
-	function onMouseUp( event ) {
+			if ( scope.enabled === false ) return;
+			event.preventDefault();
+			var state = _keyState !== STATE.NONE ? _keyState : _state;
 
-		if ( scope.enabled === false ) return;
+			if ( state === STATE.ROTATE && ! scope.noRotate ) {
 
-		event.preventDefault();
+				_movePrev.copy( _moveCurr );
 
-		_state = STATE.NONE;
+				_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
 
-		scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
-		scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
+			} else if ( state === STATE.ZOOM && ! scope.noZoom ) {
 
-		scope.dispatchEvent( endEvent );
+				_zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
 
-	}
+			} else if ( state === STATE.PAN && ! scope.noPan ) {
 
-	function mousewheel( event ) {
+				_panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
 
-		if ( scope.enabled === false ) return;
+			}
 
-		if ( scope.noZoom === true ) return;
+		}
 
-		event.preventDefault();
+		function onMouseUp( event ) {
 
-		switch ( event.deltaMode ) {
+			if ( scope.enabled === false ) return;
+			event.preventDefault();
+			_state = STATE.NONE;
+			scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
+			scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
+			scope.dispatchEvent( endEvent );
 
-			case 2:
+		}
+
+		function mousewheel( event ) {
+
+			if ( scope.enabled === false ) return;
+			if ( scope.noZoom === true ) return;
+			event.preventDefault();
+
+			switch ( event.deltaMode ) {
+
+				case 2:
 				// Zoom in pages
-				_zoomStart.y -= event.deltaY * 0.025;
-				break;
+					_zoomStart.y -= event.deltaY * 0.025;
+					break;
 
-			case 1:
+				case 1:
 				// Zoom in lines
-				_zoomStart.y -= event.deltaY * 0.01;
-				break;
+					_zoomStart.y -= event.deltaY * 0.01;
+					break;
 
-			default:
+				default:
 				// undefined, 0, assume pixels
-				_zoomStart.y -= event.deltaY * 0.00025;
-				break;
+					_zoomStart.y -= event.deltaY * 0.00025;
+					break;
 
-		}
+			}
 
-		scope.dispatchEvent( startEvent );
-		scope.dispatchEvent( endEvent );
+			scope.dispatchEvent( startEvent );
+			scope.dispatchEvent( endEvent );
 
-	}
+		}
 
-	function touchstart( event ) {
+		function touchstart( event ) {
 
-		if ( scope.enabled === false ) return;
+			if ( scope.enabled === false ) return;
+			event.preventDefault();
 
-		event.preventDefault();
+			switch ( event.touches.length ) {
 
-		switch ( event.touches.length ) {
+				case 1:
+					_state = STATE.TOUCH_ROTATE;
 
-			case 1:
-				_state = STATE.TOUCH_ROTATE;
-				_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
-				_movePrev.copy( _moveCurr );
-				break;
+					_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
 
-			default: // 2 or more
-				_state = STATE.TOUCH_ZOOM_PAN;
-				var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
-				var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
-				_touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
+					_movePrev.copy( _moveCurr );
 
-				var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
-				var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
-				_panStart.copy( getMouseOnScreen( x, y ) );
-				_panEnd.copy( _panStart );
-				break;
+					break;
 
-		}
+				default:
+				// 2 or more
+					_state = STATE.TOUCH_ZOOM_PAN;
+					var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+					var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+					_touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
+					var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
+					var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
 
-		scope.dispatchEvent( startEvent );
+					_panStart.copy( getMouseOnScreen( x, y ) );
 
-	}
+					_panEnd.copy( _panStart );
 
-	function touchmove( event ) {
+					break;
 
-		if ( scope.enabled === false ) return;
+			}
 
-		event.preventDefault();
+			scope.dispatchEvent( startEvent );
 
-		switch ( event.touches.length ) {
+		}
 
-			case 1:
-				_movePrev.copy( _moveCurr );
-				_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
-				break;
+		function touchmove( event ) {
 
-			default: // 2 or more
-				var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
-				var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
-				_touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy );
+			if ( scope.enabled === false ) return;
+			event.preventDefault();
 
-				var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
-				var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
-				_panEnd.copy( getMouseOnScreen( x, y ) );
-				break;
+			switch ( event.touches.length ) {
 
-		}
+				case 1:
+					_movePrev.copy( _moveCurr );
 
-	}
+					_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
 
-	function touchend( event ) {
+					break;
 
-		if ( scope.enabled === false ) return;
+				default:
+				// 2 or more
+					var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+					var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+					_touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy );
+					var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
+					var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
 
-		switch ( event.touches.length ) {
+					_panEnd.copy( getMouseOnScreen( x, y ) );
 
-			case 0:
-				_state = STATE.NONE;
-				break;
+					break;
 
-			case 1:
-				_state = STATE.TOUCH_ROTATE;
-				_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
-				_movePrev.copy( _moveCurr );
-				break;
+			}
 
 		}
 
-		scope.dispatchEvent( endEvent );
+		function touchend( event ) {
 
-	}
+			if ( scope.enabled === false ) return;
 
-	function contextmenu( event ) {
+			switch ( event.touches.length ) {
 
-		if ( scope.enabled === false ) return;
+				case 0:
+					_state = STATE.NONE;
+					break;
 
-		event.preventDefault();
+				case 1:
+					_state = STATE.TOUCH_ROTATE;
 
-	}
+					_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
 
-	this.dispose = function () {
+					_movePrev.copy( _moveCurr );
 
-		scope.domElement.removeEventListener( 'contextmenu', contextmenu );
+					break;
 
-		scope.domElement.removeEventListener( 'pointerdown', onPointerDown );
-		scope.domElement.removeEventListener( 'wheel', mousewheel );
+			}
 
-		scope.domElement.removeEventListener( 'touchstart', touchstart );
-		scope.domElement.removeEventListener( 'touchend', touchend );
-		scope.domElement.removeEventListener( 'touchmove', touchmove );
+			scope.dispatchEvent( endEvent );
 
-		scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
-		scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
+		}
 
-		window.removeEventListener( 'keydown', keydown );
-		window.removeEventListener( 'keyup', keyup );
+		function contextmenu( event ) {
 
-	};
+			if ( scope.enabled === false ) return;
+			event.preventDefault();
 
-	this.domElement.addEventListener( 'contextmenu', contextmenu );
+		}
 
-	this.domElement.addEventListener( 'pointerdown', onPointerDown );
-	this.domElement.addEventListener( 'wheel', mousewheel );
+		this.dispose = function () {
 
-	this.domElement.addEventListener( 'touchstart', touchstart );
-	this.domElement.addEventListener( 'touchend', touchend );
-	this.domElement.addEventListener( 'touchmove', touchmove );
+			scope.domElement.removeEventListener( 'contextmenu', contextmenu );
+			scope.domElement.removeEventListener( 'pointerdown', onPointerDown );
+			scope.domElement.removeEventListener( 'wheel', mousewheel );
+			scope.domElement.removeEventListener( 'touchstart', touchstart );
+			scope.domElement.removeEventListener( 'touchend', touchend );
+			scope.domElement.removeEventListener( 'touchmove', touchmove );
+			scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
+			scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
+			window.removeEventListener( 'keydown', keydown );
+			window.removeEventListener( 'keyup', keyup );
 
-	this.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove );
-	this.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp );
+		};
 
-	window.addEventListener( 'keydown', keydown );
-	window.addEventListener( 'keyup', keyup );
+		this.domElement.addEventListener( 'contextmenu', contextmenu );
+		this.domElement.addEventListener( 'pointerdown', onPointerDown );
+		this.domElement.addEventListener( 'wheel', mousewheel );
+		this.domElement.addEventListener( 'touchstart', touchstart );
+		this.domElement.addEventListener( 'touchend', touchend );
+		this.domElement.addEventListener( 'touchmove', touchmove );
+		this.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove );
+		this.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp );
+		window.addEventListener( 'keydown', keydown );
+		window.addEventListener( 'keyup', keyup );
+		this.handleResize(); // force an update at start
 
-	this.handleResize();
+		this.update();
+
+	};
 
-	// force an update at start
-	this.update();
+	TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype );
+	TrackballControls.prototype.constructor = TrackballControls;
 
-};
+	THREE.TrackballControls = TrackballControls;
 
-THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype );
-THREE.TrackballControls.prototype.constructor = THREE.TrackballControls;
+} )();

+ 928 - 1176
examples/js/controls/TransformControls.js

@@ -1,1663 +1,1415 @@
-THREE.TransformControls = function ( camera, domElement ) {
+( function () {
 
-	if ( domElement === undefined ) {
+	var TransformControls = function ( camera, domElement ) {
 
-		console.warn( 'THREE.TransformControls: The second parameter "domElement" is now mandatory.' );
-		domElement = document;
+		if ( domElement === undefined ) {
 
-	}
+			console.warn( 'THREE.TransformControls: The second parameter "domElement" is now mandatory.' );
+			domElement = document;
 
-	THREE.Object3D.call( this );
-
-	this.visible = false;
-	this.domElement = domElement;
-
-	var _gizmo = new THREE.TransformControlsGizmo();
-	this.add( _gizmo );
-
-	var _plane = new THREE.TransformControlsPlane();
-	this.add( _plane );
-
-	var scope = this;
-
-	// Define properties with getters/setter
-	// Setting the defined property will automatically trigger change event
-	// Defined properties are passed down to gizmo and plane
-
-	defineProperty( 'camera', camera );
-	defineProperty( 'object', undefined );
-	defineProperty( 'enabled', true );
-	defineProperty( 'axis', null );
-	defineProperty( 'mode', 'translate' );
-	defineProperty( 'translationSnap', null );
-	defineProperty( 'rotationSnap', null );
-	defineProperty( 'scaleSnap', null );
-	defineProperty( 'space', 'world' );
-	defineProperty( 'size', 1 );
-	defineProperty( 'dragging', false );
-	defineProperty( 'showX', true );
-	defineProperty( 'showY', true );
-	defineProperty( 'showZ', true );
-
-	var changeEvent = { type: 'change' };
-	var mouseDownEvent = { type: 'mouseDown' };
-	var mouseUpEvent = { type: 'mouseUp', mode: scope.mode };
-	var objectChangeEvent = { type: 'objectChange' };
+		}
 
-	// Reusable utility variables
+		THREE.Object3D.call( this );
+		this.visible = false;
+		this.domElement = domElement;
+
+		var _gizmo = new TransformControlsGizmo();
+
+		this.add( _gizmo );
+
+		var _plane = new TransformControlsPlane();
+
+		this.add( _plane );
+		var scope = this; // Define properties with getters/setter
+		// Setting the defined property will automatically trigger change event
+		// Defined properties are passed down to gizmo and plane
+
+		defineProperty( 'camera', camera );
+		defineProperty( 'object', undefined );
+		defineProperty( 'enabled', true );
+		defineProperty( 'axis', null );
+		defineProperty( 'mode', 'translate' );
+		defineProperty( 'translationSnap', null );
+		defineProperty( 'rotationSnap', null );
+		defineProperty( 'scaleSnap', null );
+		defineProperty( 'space', 'world' );
+		defineProperty( 'size', 1 );
+		defineProperty( 'dragging', false );
+		defineProperty( 'showX', true );
+		defineProperty( 'showY', true );
+		defineProperty( 'showZ', true );
+		var changeEvent = {
+			type: 'change'
+		};
+		var mouseDownEvent = {
+			type: 'mouseDown'
+		};
+		var mouseUpEvent = {
+			type: 'mouseUp',
+			mode: scope.mode
+		};
+		var objectChangeEvent = {
+			type: 'objectChange'
+		}; // Reusable utility variables
+
+		var raycaster = new THREE.Raycaster();
+
+		function intersectObjectWithRay( object, raycaster, includeInvisible ) {
+
+			var allIntersections = raycaster.intersectObject( object, true );
+
+			for ( var i = 0; i < allIntersections.length; i ++ ) {
+
+				if ( allIntersections[ i ].object.visible || includeInvisible ) {
+
+					return allIntersections[ i ];
 
-	var raycaster = new THREE.Raycaster();
+				}
 
-	function intersectObjectWithRay( object, raycaster, includeInvisible ) {
+			}
 
-		var allIntersections = raycaster.intersectObject( object, true );
+			return false;
 
-		for ( var i = 0; i < allIntersections.length; i ++ ) {
+		}
 
-			if ( allIntersections[ i ].object.visible || includeInvisible ) {
+		var _tempVector = new THREE.Vector3();
+
+		var _tempVector2 = new THREE.Vector3();
+
+		var _tempQuaternion = new THREE.Quaternion();
+
+		var _unit = {
+			X: new THREE.Vector3( 1, 0, 0 ),
+			Y: new THREE.Vector3( 0, 1, 0 ),
+			Z: new THREE.Vector3( 0, 0, 1 )
+		};
+		var pointStart = new THREE.Vector3();
+		var pointEnd = new THREE.Vector3();
+		var offset = new THREE.Vector3();
+		var rotationAxis = new THREE.Vector3();
+		var startNorm = new THREE.Vector3();
+		var endNorm = new THREE.Vector3();
+		var rotationAngle = 0;
+		var cameraPosition = new THREE.Vector3();
+		var cameraQuaternion = new THREE.Quaternion();
+		var cameraScale = new THREE.Vector3();
+		var parentPosition = new THREE.Vector3();
+		var parentQuaternion = new THREE.Quaternion();
+		var parentQuaternionInv = new THREE.Quaternion();
+		var parentScale = new THREE.Vector3();
+		var worldPositionStart = new THREE.Vector3();
+		var worldQuaternionStart = new THREE.Quaternion();
+		var worldScaleStart = new THREE.Vector3();
+		var worldPosition = new THREE.Vector3();
+		var worldQuaternion = new THREE.Quaternion();
+		var worldQuaternionInv = new THREE.Quaternion();
+		var worldScale = new THREE.Vector3();
+		var eye = new THREE.Vector3();
+		var positionStart = new THREE.Vector3();
+		var quaternionStart = new THREE.Quaternion();
+		var scaleStart = new THREE.Vector3(); // TODO: remove properties unused in plane and gizmo
+
+		defineProperty( 'worldPosition', worldPosition );
+		defineProperty( 'worldPositionStart', worldPositionStart );
+		defineProperty( 'worldQuaternion', worldQuaternion );
+		defineProperty( 'worldQuaternionStart', worldQuaternionStart );
+		defineProperty( 'cameraPosition', cameraPosition );
+		defineProperty( 'cameraQuaternion', cameraQuaternion );
+		defineProperty( 'pointStart', pointStart );
+		defineProperty( 'pointEnd', pointEnd );
+		defineProperty( 'rotationAxis', rotationAxis );
+		defineProperty( 'rotationAngle', rotationAngle );
+		defineProperty( 'eye', eye );
+		{
+
+			domElement.addEventListener( 'pointerdown', onPointerDown );
+			domElement.addEventListener( 'pointermove', onPointerHover );
+			scope.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp );
 
-				return allIntersections[ i ];
+		}
 
-			}
+		this.dispose = function () {
 
-		}
+			domElement.removeEventListener( 'pointerdown', onPointerDown );
+			domElement.removeEventListener( 'pointermove', onPointerHover );
+			scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
+			scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
+			this.traverse( function ( child ) {
 
-		return false;
+				if ( child.geometry ) child.geometry.dispose();
+				if ( child.material ) child.material.dispose();
 
-	}
+			} );
 
-	var _tempVector = new THREE.Vector3();
-	var _tempVector2 = new THREE.Vector3();
-	var _tempQuaternion = new THREE.Quaternion();
-	var _unit = {
-		X: new THREE.Vector3( 1, 0, 0 ),
-		Y: new THREE.Vector3( 0, 1, 0 ),
-		Z: new THREE.Vector3( 0, 0, 1 )
-	};
+		}; // Set current object
 
-	var pointStart = new THREE.Vector3();
-	var pointEnd = new THREE.Vector3();
-	var offset = new THREE.Vector3();
-	var rotationAxis = new THREE.Vector3();
-	var startNorm = new THREE.Vector3();
-	var endNorm = new THREE.Vector3();
-	var rotationAngle = 0;
 
-	var cameraPosition = new THREE.Vector3();
-	var cameraQuaternion = new THREE.Quaternion();
-	var cameraScale = new THREE.Vector3();
+		this.attach = function ( object ) {
 
-	var parentPosition = new THREE.Vector3();
-	var parentQuaternion = new THREE.Quaternion();
-	var parentQuaternionInv = new THREE.Quaternion();
-	var parentScale = new THREE.Vector3();
+			this.object = object;
+			this.visible = true;
+			return this;
 
-	var worldPositionStart = new THREE.Vector3();
-	var worldQuaternionStart = new THREE.Quaternion();
-	var worldScaleStart = new THREE.Vector3();
+		}; // Detatch from object
 
-	var worldPosition = new THREE.Vector3();
-	var worldQuaternion = new THREE.Quaternion();
-	var worldQuaternionInv = new THREE.Quaternion();
-	var worldScale = new THREE.Vector3();
 
-	var eye = new THREE.Vector3();
+		this.detach = function () {
 
-	var positionStart = new THREE.Vector3();
-	var quaternionStart = new THREE.Quaternion();
-	var scaleStart = new THREE.Vector3();
+			this.object = undefined;
+			this.visible = false;
+			this.axis = null;
+			return this;
 
-	// TODO: remove properties unused in plane and gizmo
+		}; // Defined getter, setter and store for a property
 
-	defineProperty( 'worldPosition', worldPosition );
-	defineProperty( 'worldPositionStart', worldPositionStart );
-	defineProperty( 'worldQuaternion', worldQuaternion );
-	defineProperty( 'worldQuaternionStart', worldQuaternionStart );
-	defineProperty( 'cameraPosition', cameraPosition );
-	defineProperty( 'cameraQuaternion', cameraQuaternion );
-	defineProperty( 'pointStart', pointStart );
-	defineProperty( 'pointEnd', pointEnd );
-	defineProperty( 'rotationAxis', rotationAxis );
-	defineProperty( 'rotationAngle', rotationAngle );
-	defineProperty( 'eye', eye );
 
-	{
+		function defineProperty( propName, defaultValue ) {
 
-		domElement.addEventListener( 'pointerdown', onPointerDown );
-		domElement.addEventListener( 'pointermove', onPointerHover );
-		scope.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp );
+			var propValue = defaultValue;
+			Object.defineProperty( scope, propName, {
+				get: function () {
 
-	}
+					return propValue !== undefined ? propValue : defaultValue;
 
-	this.dispose = function () {
+				},
+				set: function ( value ) {
 
-		domElement.removeEventListener( 'pointerdown', onPointerDown );
-		domElement.removeEventListener( 'pointermove', onPointerHover );
-		scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
-		scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
+					if ( propValue !== value ) {
 
-		this.traverse( function ( child ) {
+						propValue = value;
+						_plane[ propName ] = value;
+						_gizmo[ propName ] = value;
+						scope.dispatchEvent( {
+							type: propName + '-changed',
+							value: value
+						} );
+						scope.dispatchEvent( changeEvent );
 
-			if ( child.geometry ) child.geometry.dispose();
-			if ( child.material ) child.material.dispose();
+					}
 
-		} );
+				}
+			} );
+			scope[ propName ] = defaultValue;
+			_plane[ propName ] = defaultValue;
+			_gizmo[ propName ] = defaultValue;
 
-	};
+		} // updateMatrixWorld	updates key transformation variables
 
-	// Set current object
-	this.attach = function ( object ) {
 
-		this.object = object;
-		this.visible = true;
+		this.updateMatrixWorld = function () {
 
-		return this;
+			if ( this.object !== undefined ) {
 
-	};
+				this.object.updateMatrixWorld();
 
-	// Detatch from object
-	this.detach = function () {
+				if ( this.object.parent === null ) {
 
-		this.object = undefined;
-		this.visible = false;
-		this.axis = null;
+					console.error( 'TransformControls: The attached 3D object must be a part of the scene graph.' );
 
-		return this;
+				} else {
 
-	};
+					this.object.parent.matrixWorld.decompose( parentPosition, parentQuaternion, parentScale );
 
-	// Defined getter, setter and store for a property
-	function defineProperty( propName, defaultValue ) {
+				}
 
-		var propValue = defaultValue;
+				this.object.matrixWorld.decompose( worldPosition, worldQuaternion, worldScale );
+				parentQuaternionInv.copy( parentQuaternion ).invert();
+				worldQuaternionInv.copy( worldQuaternion ).invert();
 
-		Object.defineProperty( scope, propName, {
+			}
 
-			get: function () {
+			this.camera.updateMatrixWorld();
+			this.camera.matrixWorld.decompose( cameraPosition, cameraQuaternion, cameraScale );
+			eye.copy( cameraPosition ).sub( worldPosition ).normalize();
+			THREE.Object3D.prototype.updateMatrixWorld.call( this );
 
-				return propValue !== undefined ? propValue : defaultValue;
+		};
 
-			},
+		this.pointerHover = function ( pointer ) {
 
-			set: function ( value ) {
+			if ( this.object === undefined || this.dragging === true ) return;
+			raycaster.setFromCamera( pointer, this.camera );
+			var intersect = intersectObjectWithRay( _gizmo.picker[ this.mode ], raycaster );
 
-				if ( propValue !== value ) {
+			if ( intersect ) {
 
-					propValue = value;
-					_plane[ propName ] = value;
-					_gizmo[ propName ] = value;
+				this.axis = intersect.object.name;
 
-					scope.dispatchEvent( { type: propName + '-changed', value: value } );
-					scope.dispatchEvent( changeEvent );
+			} else {
 
-				}
+				this.axis = null;
 
 			}
 
-		} );
-
-		scope[ propName ] = defaultValue;
-		_plane[ propName ] = defaultValue;
-		_gizmo[ propName ] = defaultValue;
+		};
 
-	}
+		this.pointerDown = function ( pointer ) {
 
-	// updateMatrixWorld  updates key transformation variables
-	this.updateMatrixWorld = function () {
+			if ( this.object === undefined || this.dragging === true || pointer.button !== 0 ) return;
 
-		if ( this.object !== undefined ) {
+			if ( this.axis !== null ) {
 
-			this.object.updateMatrixWorld();
+				raycaster.setFromCamera( pointer, this.camera );
+				var planeIntersect = intersectObjectWithRay( _plane, raycaster, true );
 
-			if ( this.object.parent === null ) {
+				if ( planeIntersect ) {
 
-				console.error( 'TransformControls: The attached 3D object must be a part of the scene graph.' );
+					var space = this.space;
 
-			} else {
+					if ( this.mode === 'scale' ) {
 
-				this.object.parent.matrixWorld.decompose( parentPosition, parentQuaternion, parentScale );
+						space = 'local';
 
-			}
+					} else if ( this.axis === 'E' || this.axis === 'XYZE' || this.axis === 'XYZ' ) {
 
-			this.object.matrixWorld.decompose( worldPosition, worldQuaternion, worldScale );
+						space = 'world';
 
-			parentQuaternionInv.copy( parentQuaternion ).invert();
-			worldQuaternionInv.copy( worldQuaternion ).invert();
+					}
 
-		}
+					if ( space === 'local' && this.mode === 'rotate' ) {
 
-		this.camera.updateMatrixWorld();
-		this.camera.matrixWorld.decompose( cameraPosition, cameraQuaternion, cameraScale );
+						var snap = this.rotationSnap;
+						if ( this.axis === 'X' && snap ) this.object.rotation.x = Math.round( this.object.rotation.x / snap ) * snap;
+						if ( this.axis === 'Y' && snap ) this.object.rotation.y = Math.round( this.object.rotation.y / snap ) * snap;
+						if ( this.axis === 'Z' && snap ) this.object.rotation.z = Math.round( this.object.rotation.z / snap ) * snap;
 
-		eye.copy( cameraPosition ).sub( worldPosition ).normalize();
+					}
 
-		THREE.Object3D.prototype.updateMatrixWorld.call( this );
+					this.object.updateMatrixWorld();
+					this.object.parent.updateMatrixWorld();
+					positionStart.copy( this.object.position );
+					quaternionStart.copy( this.object.quaternion );
+					scaleStart.copy( this.object.scale );
+					this.object.matrixWorld.decompose( worldPositionStart, worldQuaternionStart, worldScaleStart );
+					pointStart.copy( planeIntersect.point ).sub( worldPositionStart );
 
-	};
+				}
 
-	this.pointerHover = function ( pointer ) {
+				this.dragging = true;
+				mouseDownEvent.mode = this.mode;
+				this.dispatchEvent( mouseDownEvent );
 
-		if ( this.object === undefined || this.dragging === true ) return;
+			}
 
-		raycaster.setFromCamera( pointer, this.camera );
+		};
 
-		var intersect = intersectObjectWithRay( _gizmo.picker[ this.mode ], raycaster );
+		this.pointerMove = function ( pointer ) {
 
-		if ( intersect ) {
+			var axis = this.axis;
+			var mode = this.mode;
+			var object = this.object;
+			var space = this.space;
 
-			this.axis = intersect.object.name;
+			if ( mode === 'scale' ) {
 
-		} else {
+				space = 'local';
 
-			this.axis = null;
+			} else if ( axis === 'E' || axis === 'XYZE' || axis === 'XYZ' ) {
 
-		}
+				space = 'world';
 
-	};
+			}
 
-	this.pointerDown = function ( pointer ) {
+			if ( object === undefined || axis === null || this.dragging === false || pointer.button !== - 1 ) return;
+			raycaster.setFromCamera( pointer, this.camera );
+			var planeIntersect = intersectObjectWithRay( _plane, raycaster, true );
+			if ( ! planeIntersect ) return;
+			pointEnd.copy( planeIntersect.point ).sub( worldPositionStart );
 
-		if ( this.object === undefined || this.dragging === true || pointer.button !== 0 ) return;
+			if ( mode === 'translate' ) {
 
-		if ( this.axis !== null ) {
+				// Apply translate
+				offset.copy( pointEnd ).sub( pointStart );
 
-			raycaster.setFromCamera( pointer, this.camera );
+				if ( space === 'local' && axis !== 'XYZ' ) {
 
-			var planeIntersect = intersectObjectWithRay( _plane, raycaster, true );
+					offset.applyQuaternion( worldQuaternionInv );
 
-			if ( planeIntersect ) {
+				}
 
-				var space = this.space;
+				if ( axis.indexOf( 'X' ) === - 1 ) offset.x = 0;
+				if ( axis.indexOf( 'Y' ) === - 1 ) offset.y = 0;
+				if ( axis.indexOf( 'Z' ) === - 1 ) offset.z = 0;
 
-				if ( this.mode === 'scale' ) {
+				if ( space === 'local' && axis !== 'XYZ' ) {
 
-					space = 'local';
+					offset.applyQuaternion( quaternionStart ).divide( parentScale );
 
-				} else if ( this.axis === 'E' || this.axis === 'XYZE' || this.axis === 'XYZ' ) {
+				} else {
 
-					space = 'world';
+					offset.applyQuaternion( parentQuaternionInv ).divide( parentScale );
 
 				}
 
-				if ( space === 'local' && this.mode === 'rotate' ) {
+				object.position.copy( offset ).add( positionStart ); // Apply translation snap
 
-					var snap = this.rotationSnap;
+				if ( this.translationSnap ) {
 
-					if ( this.axis === 'X' && snap ) this.object.rotation.x = Math.round( this.object.rotation.x / snap ) * snap;
-					if ( this.axis === 'Y' && snap ) this.object.rotation.y = Math.round( this.object.rotation.y / snap ) * snap;
-					if ( this.axis === 'Z' && snap ) this.object.rotation.z = Math.round( this.object.rotation.z / snap ) * snap;
+					if ( space === 'local' ) {
 
-				}
+						object.position.applyQuaternion( _tempQuaternion.copy( quaternionStart ).invert() );
 
-				this.object.updateMatrixWorld();
-				this.object.parent.updateMatrixWorld();
+						if ( axis.search( 'X' ) !== - 1 ) {
 
-				positionStart.copy( this.object.position );
-				quaternionStart.copy( this.object.quaternion );
-				scaleStart.copy( this.object.scale );
+							object.position.x = Math.round( object.position.x / this.translationSnap ) * this.translationSnap;
 
-				this.object.matrixWorld.decompose( worldPositionStart, worldQuaternionStart, worldScaleStart );
+						}
 
-				pointStart.copy( planeIntersect.point ).sub( worldPositionStart );
+						if ( axis.search( 'Y' ) !== - 1 ) {
 
-			}
+							object.position.y = Math.round( object.position.y / this.translationSnap ) * this.translationSnap;
 
-			this.dragging = true;
-			mouseDownEvent.mode = this.mode;
-			this.dispatchEvent( mouseDownEvent );
+						}
 
-		}
+						if ( axis.search( 'Z' ) !== - 1 ) {
 
-	};
+							object.position.z = Math.round( object.position.z / this.translationSnap ) * this.translationSnap;
 
-	this.pointerMove = function ( pointer ) {
-
-		var axis = this.axis;
-		var mode = this.mode;
-		var object = this.object;
-		var space = this.space;
+						}
 
-		if ( mode === 'scale' ) {
+						object.position.applyQuaternion( quaternionStart );
 
-			space = 'local';
+					}
 
-		} else if ( axis === 'E' || axis === 'XYZE' || axis === 'XYZ' ) {
+					if ( space === 'world' ) {
 
-			space = 'world';
+						if ( object.parent ) {
 
-		}
+							object.position.add( _tempVector.setFromMatrixPosition( object.parent.matrixWorld ) );
 
-		if ( object === undefined || axis === null || this.dragging === false || pointer.button !== - 1 ) return;
+						}
 
-		raycaster.setFromCamera( pointer, this.camera );
+						if ( axis.search( 'X' ) !== - 1 ) {
 
-		var planeIntersect = intersectObjectWithRay( _plane, raycaster, true );
+							object.position.x = Math.round( object.position.x / this.translationSnap ) * this.translationSnap;
 
-		if ( ! planeIntersect ) return;
+						}
 
-		pointEnd.copy( planeIntersect.point ).sub( worldPositionStart );
+						if ( axis.search( 'Y' ) !== - 1 ) {
 
-		if ( mode === 'translate' ) {
+							object.position.y = Math.round( object.position.y / this.translationSnap ) * this.translationSnap;
 
-			// Apply translate
+						}
 
-			offset.copy( pointEnd ).sub( pointStart );
+						if ( axis.search( 'Z' ) !== - 1 ) {
 
-			if ( space === 'local' && axis !== 'XYZ' ) {
+							object.position.z = Math.round( object.position.z / this.translationSnap ) * this.translationSnap;
 
-				offset.applyQuaternion( worldQuaternionInv );
+						}
 
-			}
+						if ( object.parent ) {
 
-			if ( axis.indexOf( 'X' ) === - 1 ) offset.x = 0;
-			if ( axis.indexOf( 'Y' ) === - 1 ) offset.y = 0;
-			if ( axis.indexOf( 'Z' ) === - 1 ) offset.z = 0;
+							object.position.sub( _tempVector.setFromMatrixPosition( object.parent.matrixWorld ) );
 
-			if ( space === 'local' && axis !== 'XYZ' ) {
+						}
 
-				offset.applyQuaternion( quaternionStart ).divide( parentScale );
+					}
 
-			} else {
+				}
 
-				offset.applyQuaternion( parentQuaternionInv ).divide( parentScale );
+			} else if ( mode === 'scale' ) {
 
-			}
+				if ( axis.search( 'XYZ' ) !== - 1 ) {
 
-			object.position.copy( offset ).add( positionStart );
+					var d = pointEnd.length() / pointStart.length();
+					if ( pointEnd.dot( pointStart ) < 0 ) d *= - 1;
 
-			// Apply translation snap
+					_tempVector2.set( d, d, d );
 
-			if ( this.translationSnap ) {
+				} else {
 
-				if ( space === 'local' ) {
+					_tempVector.copy( pointStart );
 
-					object.position.applyQuaternion( _tempQuaternion.copy( quaternionStart ).invert() );
+					_tempVector2.copy( pointEnd );
 
-					if ( axis.search( 'X' ) !== - 1 ) {
+					_tempVector.applyQuaternion( worldQuaternionInv );
 
-						object.position.x = Math.round( object.position.x / this.translationSnap ) * this.translationSnap;
+					_tempVector2.applyQuaternion( worldQuaternionInv );
 
-					}
+					_tempVector2.divide( _tempVector );
 
-					if ( axis.search( 'Y' ) !== - 1 ) {
+					if ( axis.search( 'X' ) === - 1 ) {
 
-						object.position.y = Math.round( object.position.y / this.translationSnap ) * this.translationSnap;
+						_tempVector2.x = 1;
 
 					}
 
-					if ( axis.search( 'Z' ) !== - 1 ) {
+					if ( axis.search( 'Y' ) === - 1 ) {
 
-						object.position.z = Math.round( object.position.z / this.translationSnap ) * this.translationSnap;
+						_tempVector2.y = 1;
 
 					}
 
-					object.position.applyQuaternion( quaternionStart );
+					if ( axis.search( 'Z' ) === - 1 ) {
 
-				}
+						_tempVector2.z = 1;
 
-				if ( space === 'world' ) {
+					}
 
-					if ( object.parent ) {
+				} // Apply scale
 
-						object.position.add( _tempVector.setFromMatrixPosition( object.parent.matrixWorld ) );
 
-					}
+				object.scale.copy( scaleStart ).multiply( _tempVector2 );
+
+				if ( this.scaleSnap ) {
 
 					if ( axis.search( 'X' ) !== - 1 ) {
 
-						object.position.x = Math.round( object.position.x / this.translationSnap ) * this.translationSnap;
+						object.scale.x = Math.round( object.scale.x / this.scaleSnap ) * this.scaleSnap || this.scaleSnap;
 
 					}
 
 					if ( axis.search( 'Y' ) !== - 1 ) {
 
-						object.position.y = Math.round( object.position.y / this.translationSnap ) * this.translationSnap;
+						object.scale.y = Math.round( object.scale.y / this.scaleSnap ) * this.scaleSnap || this.scaleSnap;
 
 					}
 
 					if ( axis.search( 'Z' ) !== - 1 ) {
 
-						object.position.z = Math.round( object.position.z / this.translationSnap ) * this.translationSnap;
-
-					}
-
-					if ( object.parent ) {
-
-						object.position.sub( _tempVector.setFromMatrixPosition( object.parent.matrixWorld ) );
+						object.scale.z = Math.round( object.scale.z / this.scaleSnap ) * this.scaleSnap || this.scaleSnap;
 
 					}
 
 				}
 
-			}
-
-		} else if ( mode === 'scale' ) {
-
-			if ( axis.search( 'XYZ' ) !== - 1 ) {
-
-				var d = pointEnd.length() / pointStart.length();
-
-				if ( pointEnd.dot( pointStart ) < 0 ) d *= - 1;
-
-				_tempVector2.set( d, d, d );
+			} else if ( mode === 'rotate' ) {
 
-			} else {
-
-				_tempVector.copy( pointStart );
-				_tempVector2.copy( pointEnd );
-
-				_tempVector.applyQuaternion( worldQuaternionInv );
-				_tempVector2.applyQuaternion( worldQuaternionInv );
-
-				_tempVector2.divide( _tempVector );
-
-				if ( axis.search( 'X' ) === - 1 ) {
-
-					_tempVector2.x = 1;
+				offset.copy( pointEnd ).sub( pointStart );
+				var ROTATION_SPEED = 20 / worldPosition.distanceTo( _tempVector.setFromMatrixPosition( this.camera.matrixWorld ) );
 
-				}
+				if ( axis === 'E' ) {
 
-				if ( axis.search( 'Y' ) === - 1 ) {
+					rotationAxis.copy( eye );
+					rotationAngle = pointEnd.angleTo( pointStart );
+					startNorm.copy( pointStart ).normalize();
+					endNorm.copy( pointEnd ).normalize();
+					rotationAngle *= endNorm.cross( startNorm ).dot( eye ) < 0 ? 1 : - 1;
 
-					_tempVector2.y = 1;
+				} else if ( axis === 'XYZE' ) {
 
-				}
+					rotationAxis.copy( offset ).cross( eye ).normalize();
+					rotationAngle = offset.dot( _tempVector.copy( rotationAxis ).cross( this.eye ) ) * ROTATION_SPEED;
 
-				if ( axis.search( 'Z' ) === - 1 ) {
+				} else if ( axis === 'X' || axis === 'Y' || axis === 'Z' ) {
 
-					_tempVector2.z = 1;
+					rotationAxis.copy( _unit[ axis ] );
 
-				}
+					_tempVector.copy( _unit[ axis ] );
 
-			}
+					if ( space === 'local' ) {
 
-			// Apply scale
+						_tempVector.applyQuaternion( worldQuaternion );
 
-			object.scale.copy( scaleStart ).multiply( _tempVector2 );
+					}
 
-			if ( this.scaleSnap ) {
+					rotationAngle = offset.dot( _tempVector.cross( eye ).normalize() ) * ROTATION_SPEED;
 
-				if ( axis.search( 'X' ) !== - 1 ) {
+				} // Apply rotation snap
 
-					object.scale.x = Math.round( object.scale.x / this.scaleSnap ) * this.scaleSnap || this.scaleSnap;
 
-				}
+				if ( this.rotationSnap ) rotationAngle = Math.round( rotationAngle / this.rotationSnap ) * this.rotationSnap;
+				this.rotationAngle = rotationAngle; // Apply rotate
 
-				if ( axis.search( 'Y' ) !== - 1 ) {
+				if ( space === 'local' && axis !== 'E' && axis !== 'XYZE' ) {
 
-					object.scale.y = Math.round( object.scale.y / this.scaleSnap ) * this.scaleSnap || this.scaleSnap;
+					object.quaternion.copy( quaternionStart );
+					object.quaternion.multiply( _tempQuaternion.setFromAxisAngle( rotationAxis, rotationAngle ) ).normalize();
 
-				}
-
-				if ( axis.search( 'Z' ) !== - 1 ) {
+				} else {
 
-					object.scale.z = Math.round( object.scale.z / this.scaleSnap ) * this.scaleSnap || this.scaleSnap;
+					rotationAxis.applyQuaternion( parentQuaternionInv );
+					object.quaternion.copy( _tempQuaternion.setFromAxisAngle( rotationAxis, rotationAngle ) );
+					object.quaternion.multiply( quaternionStart ).normalize();
 
 				}
 
 			}
 
-		} else if ( mode === 'rotate' ) {
+			this.dispatchEvent( changeEvent );
+			this.dispatchEvent( objectChangeEvent );
 
-			offset.copy( pointEnd ).sub( pointStart );
+		};
 
-			var ROTATION_SPEED = 20 / worldPosition.distanceTo( _tempVector.setFromMatrixPosition( this.camera.matrixWorld ) );
+		this.pointerUp = function ( pointer ) {
 
-			if ( axis === 'E' ) {
+			if ( pointer.button !== 0 ) return;
 
-				rotationAxis.copy( eye );
-				rotationAngle = pointEnd.angleTo( pointStart );
+			if ( this.dragging && this.axis !== null ) {
 
-				startNorm.copy( pointStart ).normalize();
-				endNorm.copy( pointEnd ).normalize();
+				mouseUpEvent.mode = this.mode;
+				this.dispatchEvent( mouseUpEvent );
 
-				rotationAngle *= ( endNorm.cross( startNorm ).dot( eye ) < 0 ? 1 : - 1 );
-
-			} else if ( axis === 'XYZE' ) {
+			}
 
-				rotationAxis.copy( offset ).cross( eye ).normalize();
-				rotationAngle = offset.dot( _tempVector.copy( rotationAxis ).cross( this.eye ) ) * ROTATION_SPEED;
+			this.dragging = false;
+			this.axis = null;
 
-			} else if ( axis === 'X' || axis === 'Y' || axis === 'Z' ) {
+		}; // normalize mouse / touch pointer and remap {x,y} to view space.
 
-				rotationAxis.copy( _unit[ axis ] );
 
-				_tempVector.copy( _unit[ axis ] );
+		function getPointer( event ) {
 
-				if ( space === 'local' ) {
+			if ( scope.domElement.ownerDocument.pointerLockElement ) {
 
-					_tempVector.applyQuaternion( worldQuaternion );
+				return {
+					x: 0,
+					y: 0,
+					button: event.button
+				};
 
-				}
+			} else {
 
-				rotationAngle = offset.dot( _tempVector.cross( eye ).normalize() ) * ROTATION_SPEED;
+				var pointer = event.changedTouches ? event.changedTouches[ 0 ] : event;
+				var rect = domElement.getBoundingClientRect();
+				return {
+					x: ( pointer.clientX - rect.left ) / rect.width * 2 - 1,
+					y: - ( pointer.clientY - rect.top ) / rect.height * 2 + 1,
+					button: event.button
+				};
 
 			}
 
-			// Apply rotation snap
+		} // mouse / touch event handlers
 
-			if ( this.rotationSnap ) rotationAngle = Math.round( rotationAngle / this.rotationSnap ) * this.rotationSnap;
 
-			this.rotationAngle = rotationAngle;
+		function onPointerHover( event ) {
 
-			// Apply rotate
-			if ( space === 'local' && axis !== 'E' && axis !== 'XYZE' ) {
+			if ( ! scope.enabled ) return;
 
-				object.quaternion.copy( quaternionStart );
-				object.quaternion.multiply( _tempQuaternion.setFromAxisAngle( rotationAxis, rotationAngle ) ).normalize();
+			switch ( event.pointerType ) {
 
-			} else {
-
-				rotationAxis.applyQuaternion( parentQuaternionInv );
-				object.quaternion.copy( _tempQuaternion.setFromAxisAngle( rotationAxis, rotationAngle ) );
-				object.quaternion.multiply( quaternionStart ).normalize();
+				case 'mouse':
+				case 'pen':
+					scope.pointerHover( getPointer( event ) );
+					break;
 
 			}
 
 		}
 
-		this.dispatchEvent( changeEvent );
-		this.dispatchEvent( objectChangeEvent );
-
-	};
-
-	this.pointerUp = function ( pointer ) {
-
-		if ( pointer.button !== 0 ) return;
+		function onPointerDown( event ) {
 
-		if ( this.dragging && ( this.axis !== null ) ) {
+			if ( ! scope.enabled ) return;
+			scope.domElement.style.touchAction = 'none'; // disable touch scroll
 
-			mouseUpEvent.mode = this.mode;
-			this.dispatchEvent( mouseUpEvent );
+			scope.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove );
+			scope.pointerHover( getPointer( event ) );
+			scope.pointerDown( getPointer( event ) );
 
 		}
 
-		this.dragging = false;
-		this.axis = null;
-
-	};
-
-	// normalize mouse / touch pointer and remap {x,y} to view space.
-
-	function getPointer( event ) {
-
-		if ( scope.domElement.ownerDocument.pointerLockElement ) {
-
-			return {
-				x: 0,
-				y: 0,
-				button: event.button
-			};
-
-		} else {
-
-			var pointer = event.changedTouches ? event.changedTouches[ 0 ] : event;
-
-			var rect = domElement.getBoundingClientRect();
+		function onPointerMove( event ) {
 
-			return {
-				x: ( pointer.clientX - rect.left ) / rect.width * 2 - 1,
-				y: - ( pointer.clientY - rect.top ) / rect.height * 2 + 1,
-				button: event.button
-			};
+			if ( ! scope.enabled ) return;
+			scope.pointerMove( getPointer( event ) );
 
 		}
 
-	}
+		function onPointerUp( event ) {
 
-	// mouse / touch event handlers
+			if ( ! scope.enabled ) return;
+			scope.domElement.style.touchAction = '';
+			scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
+			scope.pointerUp( getPointer( event ) );
 
-	function onPointerHover( event ) {
+		} // TODO: deprecate
 
-		if ( ! scope.enabled ) return;
 
-		switch ( event.pointerType ) {
+		this.getMode = function () {
 
-			case 'mouse':
-			case 'pen':
-				scope.pointerHover( getPointer( event ) );
-				break;
+			return scope.mode;
 
-		}
-
-	}
-
-	function onPointerDown( event ) {
+		};
 
-		if ( ! scope.enabled ) return;
+		this.setMode = function ( mode ) {
 
-		scope.domElement.style.touchAction = 'none'; // disable touch scroll
-		scope.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove );
+			scope.mode = mode;
 
-		scope.pointerHover( getPointer( event ) );
-		scope.pointerDown( getPointer( event ) );
+		};
 
-	}
+		this.setTranslationSnap = function ( translationSnap ) {
 
-	function onPointerMove( event ) {
+			scope.translationSnap = translationSnap;
 
-		if ( ! scope.enabled ) return;
+		};
 
-		scope.pointerMove( getPointer( event ) );
+		this.setRotationSnap = function ( rotationSnap ) {
 
-	}
+			scope.rotationSnap = rotationSnap;
 
-	function onPointerUp( event ) {
+		};
 
-		if ( ! scope.enabled ) return;
+		this.setScaleSnap = function ( scaleSnap ) {
 
-		scope.domElement.style.touchAction = '';
-		scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
+			scope.scaleSnap = scaleSnap;
 
-		scope.pointerUp( getPointer( event ) );
+		};
 
-	}
+		this.setSize = function ( size ) {
 
-	// TODO: deprecate
+			scope.size = size;
 
-	this.getMode = function () {
+		};
 
-		return scope.mode;
+		this.setSpace = function ( space ) {
 
-	};
-
-	this.setMode = function ( mode ) {
+			scope.space = space;
 
-		scope.mode = mode;
+		};
 
-	};
+		this.update = function () {
 
-	this.setTranslationSnap = function ( translationSnap ) {
+			console.warn( 'THREE.TransformControls: update function has no more functionality and therefore has been deprecated.' );
 
-		scope.translationSnap = translationSnap;
+		};
 
 	};
 
-	this.setRotationSnap = function ( rotationSnap ) {
-
-		scope.rotationSnap = rotationSnap;
-
-	};
-
-	this.setScaleSnap = function ( scaleSnap ) {
-
-		scope.scaleSnap = scaleSnap;
-
-	};
-
-	this.setSize = function ( size ) {
-
-		scope.size = size;
-
-	};
-
-	this.setSpace = function ( space ) {
-
-		scope.space = space;
-
-	};
-
-	this.update = function () {
-
-		console.warn( 'THREE.TransformControls: update function has no more functionality and therefore has been deprecated.' );
-
-	};
-
-};
-
-THREE.TransformControls.prototype = Object.assign( Object.create( THREE.Object3D.prototype ), {
-
-	constructor: THREE.TransformControls,
-
-	isTransformControls: true
-
-} );
-
-
-THREE.TransformControlsGizmo = function () {
-
-	'use strict';
-
-	THREE.Object3D.call( this );
-
-	this.type = 'TransformControlsGizmo';
-
-	// shared materials
-
-	var gizmoMaterial = new THREE.MeshBasicMaterial( {
-		depthTest: false,
-		depthWrite: false,
-		transparent: true,
-		side: THREE.DoubleSide,
-		fog: false,
-		toneMapped: false
+	TransformControls.prototype = Object.assign( Object.create( THREE.Object3D.prototype ), {
+		constructor: TransformControls,
+		isTransformControls: true
 	} );
 
-	var gizmoLineMaterial = new THREE.LineBasicMaterial( {
-		depthTest: false,
-		depthWrite: false,
-		transparent: true,
-		linewidth: 1,
-		fog: false,
-		toneMapped: false
-	} );
-
-	// Make unique material for each axis/color
-
-	var matInvisible = gizmoMaterial.clone();
-	matInvisible.opacity = 0.15;
-
-	var matHelper = gizmoMaterial.clone();
-	matHelper.opacity = 0.33;
-
-	var matRed = gizmoMaterial.clone();
-	matRed.color.set( 0xff0000 );
-
-	var matGreen = gizmoMaterial.clone();
-	matGreen.color.set( 0x00ff00 );
-
-	var matBlue = gizmoMaterial.clone();
-	matBlue.color.set( 0x0000ff );
-
-	var matWhiteTransparent = gizmoMaterial.clone();
-	matWhiteTransparent.opacity = 0.25;
-
-	var matYellowTransparent = matWhiteTransparent.clone();
-	matYellowTransparent.color.set( 0xffff00 );
-
-	var matCyanTransparent = matWhiteTransparent.clone();
-	matCyanTransparent.color.set( 0x00ffff );
-
-	var matMagentaTransparent = matWhiteTransparent.clone();
-	matMagentaTransparent.color.set( 0xff00ff );
-
-	var matYellow = gizmoMaterial.clone();
-	matYellow.color.set( 0xffff00 );
-
-	var matLineRed = gizmoLineMaterial.clone();
-	matLineRed.color.set( 0xff0000 );
-
-	var matLineGreen = gizmoLineMaterial.clone();
-	matLineGreen.color.set( 0x00ff00 );
-
-	var matLineBlue = gizmoLineMaterial.clone();
-	matLineBlue.color.set( 0x0000ff );
-
-	var matLineCyan = gizmoLineMaterial.clone();
-	matLineCyan.color.set( 0x00ffff );
-
-	var matLineMagenta = gizmoLineMaterial.clone();
-	matLineMagenta.color.set( 0xff00ff );
-
-	var matLineYellow = gizmoLineMaterial.clone();
-	matLineYellow.color.set( 0xffff00 );
-
-	var matLineGray = gizmoLineMaterial.clone();
-	matLineGray.color.set( 0x787878 );
+	var TransformControlsGizmo = function () {
 
-	var matLineYellowTransparent = matLineYellow.clone();
-	matLineYellowTransparent.opacity = 0.25;
+		'use strict';
 
-	// reusable geometry
+		THREE.Object3D.call( this );
+		this.type = 'TransformControlsGizmo'; // shared materials
 
-	var arrowGeometry = new THREE.CylinderGeometry( 0, 0.05, 0.2, 12, 1, false );
+		var gizmoMaterial = new THREE.MeshBasicMaterial( {
+			depthTest: false,
+			depthWrite: false,
+			transparent: true,
+			side: THREE.DoubleSide,
+			fog: false,
+			toneMapped: false
+		} );
+		var gizmoLineMaterial = new THREE.LineBasicMaterial( {
+			depthTest: false,
+			depthWrite: false,
+			transparent: true,
+			linewidth: 1,
+			fog: false,
+			toneMapped: false
+		} ); // Make unique material for each axis/color
+
+		var matInvisible = gizmoMaterial.clone();
+		matInvisible.opacity = 0.15;
+		var matHelper = gizmoMaterial.clone();
+		matHelper.opacity = 0.33;
+		var matRed = gizmoMaterial.clone();
+		matRed.color.set( 0xff0000 );
+		var matGreen = gizmoMaterial.clone();
+		matGreen.color.set( 0x00ff00 );
+		var matBlue = gizmoMaterial.clone();
+		matBlue.color.set( 0x0000ff );
+		var matWhiteTransparent = gizmoMaterial.clone();
+		matWhiteTransparent.opacity = 0.25;
+		var matYellowTransparent = matWhiteTransparent.clone();
+		matYellowTransparent.color.set( 0xffff00 );
+		var matCyanTransparent = matWhiteTransparent.clone();
+		matCyanTransparent.color.set( 0x00ffff );
+		var matMagentaTransparent = matWhiteTransparent.clone();
+		matMagentaTransparent.color.set( 0xff00ff );
+		var matYellow = gizmoMaterial.clone();
+		matYellow.color.set( 0xffff00 );
+		var matLineRed = gizmoLineMaterial.clone();
+		matLineRed.color.set( 0xff0000 );
+		var matLineGreen = gizmoLineMaterial.clone();
+		matLineGreen.color.set( 0x00ff00 );
+		var matLineBlue = gizmoLineMaterial.clone();
+		matLineBlue.color.set( 0x0000ff );
+		var matLineCyan = gizmoLineMaterial.clone();
+		matLineCyan.color.set( 0x00ffff );
+		var matLineMagenta = gizmoLineMaterial.clone();
+		matLineMagenta.color.set( 0xff00ff );
+		var matLineYellow = gizmoLineMaterial.clone();
+		matLineYellow.color.set( 0xffff00 );
+		var matLineGray = gizmoLineMaterial.clone();
+		matLineGray.color.set( 0x787878 );
+		var matLineYellowTransparent = matLineYellow.clone();
+		matLineYellowTransparent.opacity = 0.25; // reusable geometry
+
+		var arrowGeometry = new THREE.CylinderGeometry( 0, 0.05, 0.2, 12, 1, false );
+		var scaleHandleGeometry = new THREE.BoxGeometry( 0.125, 0.125, 0.125 );
+		var lineGeometry = new THREE.BufferGeometry();
+		lineGeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( [ 0, 0, 0, 1, 0, 0 ], 3 ) );
+
+		var CircleGeometry = function ( radius, arc ) {
+
+			var geometry = new THREE.BufferGeometry();
+			var vertices = [];
+
+			for ( var i = 0; i <= 64 * arc; ++ i ) {
+
+				vertices.push( 0, Math.cos( i / 32 * Math.PI ) * radius, Math.sin( i / 32 * Math.PI ) * radius );
 
-	var scaleHandleGeometry = new THREE.BoxGeometry( 0.125, 0.125, 0.125 );
+			}
 
-	var lineGeometry = new THREE.BufferGeometry();
-	lineGeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( [ 0, 0, 0,	1, 0, 0 ], 3 ) );
+			geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
+			return geometry;
+
+		}; // Special geometry for transform helper. If scaled with position vector it spans from [0,0,0] to position
+
+
+		var TranslateHelperGeometry = function () {
+
+			var geometry = new THREE.BufferGeometry();
+			geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( [ 0, 0, 0, 1, 1, 1 ], 3 ) );
+			return geometry;
+
+		}; // Gizmo definitions - custom hierarchy definitions for setupGizmo() function
+
+
+		var gizmoTranslate = {
+			X: [[ new THREE.Mesh( arrowGeometry, matRed ), [ 1, 0, 0 ], [ 0, 0, - Math.PI / 2 ], null, 'fwd' ], [ new THREE.Mesh( arrowGeometry, matRed ), [ 1, 0, 0 ], [ 0, 0, Math.PI / 2 ], null, 'bwd' ], [ new THREE.Line( lineGeometry, matLineRed ) ]],
+			Y: [[ new THREE.Mesh( arrowGeometry, matGreen ), [ 0, 1, 0 ], null, null, 'fwd' ], [ new THREE.Mesh( arrowGeometry, matGreen ), [ 0, 1, 0 ], [ Math.PI, 0, 0 ], null, 'bwd' ], [ new THREE.Line( lineGeometry, matLineGreen ), null, [ 0, 0, Math.PI / 2 ]]],
+			Z: [[ new THREE.Mesh( arrowGeometry, matBlue ), [ 0, 0, 1 ], [ Math.PI / 2, 0, 0 ], null, 'fwd' ], [ new THREE.Mesh( arrowGeometry, matBlue ), [ 0, 0, 1 ], [ - Math.PI / 2, 0, 0 ], null, 'bwd' ], [ new THREE.Line( lineGeometry, matLineBlue ), null, [ 0, - Math.PI / 2, 0 ]]],
+			XYZ: [[ new THREE.Mesh( new THREE.OctahedronGeometry( 0.1, 0 ), matWhiteTransparent.clone() ), [ 0, 0, 0 ], [ 0, 0, 0 ]]],
+			XY: [[ new THREE.Mesh( new THREE.PlaneGeometry( 0.295, 0.295 ), matYellowTransparent.clone() ), [ 0.15, 0.15, 0 ]], [ new THREE.Line( lineGeometry, matLineYellow ), [ 0.18, 0.3, 0 ], null, [ 0.125, 1, 1 ]], [ new THREE.Line( lineGeometry, matLineYellow ), [ 0.3, 0.18, 0 ], [ 0, 0, Math.PI / 2 ], [ 0.125, 1, 1 ]]],
+			YZ: [[ new THREE.Mesh( new THREE.PlaneGeometry( 0.295, 0.295 ), matCyanTransparent.clone() ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]], [ new THREE.Line( lineGeometry, matLineCyan ), [ 0, 0.18, 0.3 ], [ 0, 0, Math.PI / 2 ], [ 0.125, 1, 1 ]], [ new THREE.Line( lineGeometry, matLineCyan ), [ 0, 0.3, 0.18 ], [ 0, - Math.PI / 2, 0 ], [ 0.125, 1, 1 ]]],
+			XZ: [[ new THREE.Mesh( new THREE.PlaneGeometry( 0.295, 0.295 ), matMagentaTransparent.clone() ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]], [ new THREE.Line( lineGeometry, matLineMagenta ), [ 0.18, 0, 0.3 ], null, [ 0.125, 1, 1 ]], [ new THREE.Line( lineGeometry, matLineMagenta ), [ 0.3, 0, 0.18 ], [ 0, - Math.PI / 2, 0 ], [ 0.125, 1, 1 ]]]
+		};
+		var pickerTranslate = {
+			X: [[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), matInvisible ), [ 0.6, 0, 0 ], [ 0, 0, - Math.PI / 2 ]]],
+			Y: [[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), matInvisible ), [ 0, 0.6, 0 ]]],
+			Z: [[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), matInvisible ), [ 0, 0, 0.6 ], [ Math.PI / 2, 0, 0 ]]],
+			XYZ: [[ new THREE.Mesh( new THREE.OctahedronGeometry( 0.2, 0 ), matInvisible ) ]],
+			XY: [[ new THREE.Mesh( new THREE.PlaneGeometry( 0.4, 0.4 ), matInvisible ), [ 0.2, 0.2, 0 ]]],
+			YZ: [[ new THREE.Mesh( new THREE.PlaneGeometry( 0.4, 0.4 ), matInvisible ), [ 0, 0.2, 0.2 ], [ 0, Math.PI / 2, 0 ]]],
+			XZ: [[ new THREE.Mesh( new THREE.PlaneGeometry( 0.4, 0.4 ), matInvisible ), [ 0.2, 0, 0.2 ], [ - Math.PI / 2, 0, 0 ]]]
+		};
+		var helperTranslate = {
+			START: [[ new THREE.Mesh( new THREE.OctahedronGeometry( 0.01, 2 ), matHelper ), null, null, null, 'helper' ]],
+			END: [[ new THREE.Mesh( new THREE.OctahedronGeometry( 0.01, 2 ), matHelper ), null, null, null, 'helper' ]],
+			DELTA: [[ new THREE.Line( TranslateHelperGeometry(), matHelper ), null, null, null, 'helper' ]],
+			X: [[ new THREE.Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]],
+			Y: [[ new THREE.Line( lineGeometry, matHelper.clone() ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ]],
+			Z: [[ new THREE.Line( lineGeometry, matHelper.clone() ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ]]
+		};
+		var gizmoRotate = {
+			X: [[ new THREE.Line( CircleGeometry( 1, 0.5 ), matLineRed ) ], [ new THREE.Mesh( new THREE.OctahedronGeometry( 0.04, 0 ), matRed ), [ 0, 0, 0.99 ], null, [ 1, 3, 1 ]]],
+			Y: [[ new THREE.Line( CircleGeometry( 1, 0.5 ), matLineGreen ), null, [ 0, 0, - Math.PI / 2 ]], [ new THREE.Mesh( new THREE.OctahedronGeometry( 0.04, 0 ), matGreen ), [ 0, 0, 0.99 ], null, [ 3, 1, 1 ]]],
+			Z: [[ new THREE.Line( CircleGeometry( 1, 0.5 ), matLineBlue ), null, [ 0, Math.PI / 2, 0 ]], [ new THREE.Mesh( new THREE.OctahedronGeometry( 0.04, 0 ), matBlue ), [ 0.99, 0, 0 ], null, [ 1, 3, 1 ]]],
+			E: [[ new THREE.Line( CircleGeometry( 1.25, 1 ), matLineYellowTransparent ), null, [ 0, Math.PI / 2, 0 ]], [ new THREE.Mesh( new THREE.CylinderGeometry( 0.03, 0, 0.15, 4, 1, false ), matLineYellowTransparent ), [ 1.17, 0, 0 ], [ 0, 0, - Math.PI / 2 ], [ 1, 1, 0.001 ]], [ new THREE.Mesh( new THREE.CylinderGeometry( 0.03, 0, 0.15, 4, 1, false ), matLineYellowTransparent ), [ - 1.17, 0, 0 ], [ 0, 0, Math.PI / 2 ], [ 1, 1, 0.001 ]], [ new THREE.Mesh( new THREE.CylinderGeometry( 0.03, 0, 0.15, 4, 1, false ), matLineYellowTransparent ), [ 0, - 1.17, 0 ], [ Math.PI, 0, 0 ], [ 1, 1, 0.001 ]], [ new THREE.Mesh( new THREE.CylinderGeometry( 0.03, 0, 0.15, 4, 1, false ), matLineYellowTransparent ), [ 0, 1.17, 0 ], [ 0, 0, 0 ], [ 1, 1, 0.001 ]]],
+			XYZE: [[ new THREE.Line( CircleGeometry( 1, 1 ), matLineGray ), null, [ 0, Math.PI / 2, 0 ]]]
+		};
+		var helperRotate = {
+			AXIS: [[ new THREE.Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]]
+		};
+		var pickerRotate = {
+			X: [[ new THREE.Mesh( new THREE.TorusGeometry( 1, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, - Math.PI / 2, - Math.PI / 2 ]]],
+			Y: [[ new THREE.Mesh( new THREE.TorusGeometry( 1, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ]]],
+			Z: [[ new THREE.Mesh( new THREE.TorusGeometry( 1, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]]],
+			E: [[ new THREE.Mesh( new THREE.TorusGeometry( 1.25, 0.1, 2, 24 ), matInvisible ) ]],
+			XYZE: [[ new THREE.Mesh( new THREE.SphereGeometry( 0.7, 10, 8 ), matInvisible ) ]]
+		};
+		var gizmoScale = {
+			X: [[ new THREE.Mesh( scaleHandleGeometry, matRed ), [ 0.8, 0, 0 ], [ 0, 0, - Math.PI / 2 ]], [ new THREE.Line( lineGeometry, matLineRed ), null, null, [ 0.8, 1, 1 ]]],
+			Y: [[ new THREE.Mesh( scaleHandleGeometry, matGreen ), [ 0, 0.8, 0 ]], [ new THREE.Line( lineGeometry, matLineGreen ), null, [ 0, 0, Math.PI / 2 ], [ 0.8, 1, 1 ]]],
+			Z: [[ new THREE.Mesh( scaleHandleGeometry, matBlue ), [ 0, 0, 0.8 ], [ Math.PI / 2, 0, 0 ]], [ new THREE.Line( lineGeometry, matLineBlue ), null, [ 0, - Math.PI / 2, 0 ], [ 0.8, 1, 1 ]]],
+			XY: [[ new THREE.Mesh( scaleHandleGeometry, matYellowTransparent ), [ 0.85, 0.85, 0 ], null, [ 2, 2, 0.2 ]], [ new THREE.Line( lineGeometry, matLineYellow ), [ 0.855, 0.98, 0 ], null, [ 0.125, 1, 1 ]], [ new THREE.Line( lineGeometry, matLineYellow ), [ 0.98, 0.855, 0 ], [ 0, 0, Math.PI / 2 ], [ 0.125, 1, 1 ]]],
+			YZ: [[ new THREE.Mesh( scaleHandleGeometry, matCyanTransparent ), [ 0, 0.85, 0.85 ], null, [ 0.2, 2, 2 ]], [ new THREE.Line( lineGeometry, matLineCyan ), [ 0, 0.855, 0.98 ], [ 0, 0, Math.PI / 2 ], [ 0.125, 1, 1 ]], [ new THREE.Line( lineGeometry, matLineCyan ), [ 0, 0.98, 0.855 ], [ 0, - Math.PI / 2, 0 ], [ 0.125, 1, 1 ]]],
+			XZ: [[ new THREE.Mesh( scaleHandleGeometry, matMagentaTransparent ), [ 0.85, 0, 0.85 ], null, [ 2, 0.2, 2 ]], [ new THREE.Line( lineGeometry, matLineMagenta ), [ 0.855, 0, 0.98 ], null, [ 0.125, 1, 1 ]], [ new THREE.Line( lineGeometry, matLineMagenta ), [ 0.98, 0, 0.855 ], [ 0, - Math.PI / 2, 0 ], [ 0.125, 1, 1 ]]],
+			XYZX: [[ new THREE.Mesh( new THREE.BoxGeometry( 0.125, 0.125, 0.125 ), matWhiteTransparent.clone() ), [ 1.1, 0, 0 ]]],
+			XYZY: [[ new THREE.Mesh( new THREE.BoxGeometry( 0.125, 0.125, 0.125 ), matWhiteTransparent.clone() ), [ 0, 1.1, 0 ]]],
+			XYZZ: [[ new THREE.Mesh( new THREE.BoxGeometry( 0.125, 0.125, 0.125 ), matWhiteTransparent.clone() ), [ 0, 0, 1.1 ]]]
+		};
+		var pickerScale = {
+			X: [[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 0.8, 4, 1, false ), matInvisible ), [ 0.5, 0, 0 ], [ 0, 0, - Math.PI / 2 ]]],
+			Y: [[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 0.8, 4, 1, false ), matInvisible ), [ 0, 0.5, 0 ]]],
+			Z: [[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 0.8, 4, 1, false ), matInvisible ), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ]]],
+			XY: [[ new THREE.Mesh( scaleHandleGeometry, matInvisible ), [ 0.85, 0.85, 0 ], null, [ 3, 3, 0.2 ]]],
+			YZ: [[ new THREE.Mesh( scaleHandleGeometry, matInvisible ), [ 0, 0.85, 0.85 ], null, [ 0.2, 3, 3 ]]],
+			XZ: [[ new THREE.Mesh( scaleHandleGeometry, matInvisible ), [ 0.85, 0, 0.85 ], null, [ 3, 0.2, 3 ]]],
+			XYZX: [[ new THREE.Mesh( new THREE.BoxGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 1.1, 0, 0 ]]],
+			XYZY: [[ new THREE.Mesh( new THREE.BoxGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 0, 1.1, 0 ]]],
+			XYZZ: [[ new THREE.Mesh( new THREE.BoxGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 0, 0, 1.1 ]]]
+		};
+		var helperScale = {
+			X: [[ new THREE.Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]],
+			Y: [[ new THREE.Line( lineGeometry, matHelper.clone() ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ]],
+			Z: [[ new THREE.Line( lineGeometry, matHelper.clone() ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ]]
+		}; // Creates an THREE.Object3D with gizmos described in custom hierarchy definition.
+
+		var setupGizmo = function ( gizmoMap ) {
+
+			var gizmo = new THREE.Object3D();
+
+			for ( var name in gizmoMap ) {
+
+				for ( var i = gizmoMap[ name ].length; i --; ) {
+
+					var object = gizmoMap[ name ][ i ][ 0 ].clone();
+					var position = gizmoMap[ name ][ i ][ 1 ];
+					var rotation = gizmoMap[ name ][ i ][ 2 ];
+					var scale = gizmoMap[ name ][ i ][ 3 ];
+					var tag = gizmoMap[ name ][ i ][ 4 ]; // name and tag properties are essential for picking and updating logic.
+
+					object.name = name;
+					object.tag = tag;
+
+					if ( position ) {
+
+						object.position.set( position[ 0 ], position[ 1 ], position[ 2 ] );
 
-	var CircleGeometry = function ( radius, arc ) {
+					}
 
-		var geometry = new THREE.BufferGeometry( );
-		var vertices = [];
+					if ( rotation ) {
 
-		for ( var i = 0; i <= 64 * arc; ++ i ) {
+						object.rotation.set( rotation[ 0 ], rotation[ 1 ], rotation[ 2 ] );
 
-			vertices.push( 0, Math.cos( i / 32 * Math.PI ) * radius, Math.sin( i / 32 * Math.PI ) * radius );
+					}
 
-		}
+					if ( scale ) {
 
-		geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
+						object.scale.set( scale[ 0 ], scale[ 1 ], scale[ 2 ] );
 
-		return geometry;
+					}
 
-	};
+					object.updateMatrix();
+					var tempGeometry = object.geometry.clone();
+					tempGeometry.applyMatrix4( object.matrix );
+					object.geometry = tempGeometry;
+					object.renderOrder = Infinity;
+					object.position.set( 0, 0, 0 );
+					object.rotation.set( 0, 0, 0 );
+					object.scale.set( 1, 1, 1 );
+					gizmo.add( object );
 
-	// Special geometry for transform helper. If scaled with position vector it spans from [0,0,0] to position
+				}
 
-	var TranslateHelperGeometry = function () {
+			}
 
-		var geometry = new THREE.BufferGeometry();
+			return gizmo;
 
-		geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( [ 0, 0, 0, 1, 1, 1 ], 3 ) );
+		}; // Reusable utility variables
 
-		return geometry;
 
-	};
+		var tempVector = new THREE.Vector3( 0, 0, 0 );
+		var tempEuler = new THREE.Euler();
+		var alignVector = new THREE.Vector3( 0, 1, 0 );
+		var zeroVector = new THREE.Vector3( 0, 0, 0 );
+		var lookAtMatrix = new THREE.Matrix4();
+		var tempQuaternion = new THREE.Quaternion();
+		var tempQuaternion2 = new THREE.Quaternion();
+		var identityQuaternion = new THREE.Quaternion();
+		var unitX = new THREE.Vector3( 1, 0, 0 );
+		var unitY = new THREE.Vector3( 0, 1, 0 );
+		var unitZ = new THREE.Vector3( 0, 0, 1 ); // Gizmo creation
 
-	// Gizmo definitions - custom hierarchy definitions for setupGizmo() function
-
-	var gizmoTranslate = {
-		X: [
-			[ new THREE.Mesh( arrowGeometry, matRed ), [ 1, 0, 0 ], [ 0, 0, - Math.PI / 2 ], null, 'fwd' ],
-			[ new THREE.Mesh( arrowGeometry, matRed ), [ 1, 0, 0 ], [ 0, 0, Math.PI / 2 ], null, 'bwd' ],
-			[ new THREE.Line( lineGeometry, matLineRed ) ]
-		],
-		Y: [
-			[ new THREE.Mesh( arrowGeometry, matGreen ), [ 0, 1, 0 ], null, null, 'fwd' ],
-			[ new THREE.Mesh( arrowGeometry, matGreen ), [ 0, 1, 0 ], [ Math.PI, 0, 0 ], null, 'bwd' ],
-			[ new THREE.Line( lineGeometry, matLineGreen ), null, [ 0, 0, Math.PI / 2 ]]
-		],
-		Z: [
-			[ new THREE.Mesh( arrowGeometry, matBlue ), [ 0, 0, 1 ], [ Math.PI / 2, 0, 0 ], null, 'fwd' ],
-			[ new THREE.Mesh( arrowGeometry, matBlue ), [ 0, 0, 1 ], [ - Math.PI / 2, 0, 0 ], null, 'bwd' ],
-			[ new THREE.Line( lineGeometry, matLineBlue ), null, [ 0, - Math.PI / 2, 0 ]]
-		],
-		XYZ: [
-			[ new THREE.Mesh( new THREE.OctahedronGeometry( 0.1, 0 ), matWhiteTransparent.clone() ), [ 0, 0, 0 ], [ 0, 0, 0 ]]
-		],
-		XY: [
-			[ new THREE.Mesh( new THREE.PlaneGeometry( 0.295, 0.295 ), matYellowTransparent.clone() ), [ 0.15, 0.15, 0 ]],
-			[ new THREE.Line( lineGeometry, matLineYellow ), [ 0.18, 0.3, 0 ], null, [ 0.125, 1, 1 ]],
-			[ new THREE.Line( lineGeometry, matLineYellow ), [ 0.3, 0.18, 0 ], [ 0, 0, Math.PI / 2 ], [ 0.125, 1, 1 ]]
-		],
-		YZ: [
-			[ new THREE.Mesh( new THREE.PlaneGeometry( 0.295, 0.295 ), matCyanTransparent.clone() ), [ 0, 0.15, 0.15 ], [ 0, Math.PI / 2, 0 ]],
-			[ new THREE.Line( lineGeometry, matLineCyan ), [ 0, 0.18, 0.3 ], [ 0, 0, Math.PI / 2 ], [ 0.125, 1, 1 ]],
-			[ new THREE.Line( lineGeometry, matLineCyan ), [ 0, 0.3, 0.18 ], [ 0, - Math.PI / 2, 0 ], [ 0.125, 1, 1 ]]
-		],
-		XZ: [
-			[ new THREE.Mesh( new THREE.PlaneGeometry( 0.295, 0.295 ), matMagentaTransparent.clone() ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]],
-			[ new THREE.Line( lineGeometry, matLineMagenta ), [ 0.18, 0, 0.3 ], null, [ 0.125, 1, 1 ]],
-			[ new THREE.Line( lineGeometry, matLineMagenta ), [ 0.3, 0, 0.18 ], [ 0, - Math.PI / 2, 0 ], [ 0.125, 1, 1 ]]
-		]
-	};
+		this.gizmo = {};
+		this.picker = {};
+		this.helper = {};
+		this.add( this.gizmo[ 'translate' ] = setupGizmo( gizmoTranslate ) );
+		this.add( this.gizmo[ 'rotate' ] = setupGizmo( gizmoRotate ) );
+		this.add( this.gizmo[ 'scale' ] = setupGizmo( gizmoScale ) );
+		this.add( this.picker[ 'translate' ] = setupGizmo( pickerTranslate ) );
+		this.add( this.picker[ 'rotate' ] = setupGizmo( pickerRotate ) );
+		this.add( this.picker[ 'scale' ] = setupGizmo( pickerScale ) );
+		this.add( this.helper[ 'translate' ] = setupGizmo( helperTranslate ) );
+		this.add( this.helper[ 'rotate' ] = setupGizmo( helperRotate ) );
+		this.add( this.helper[ 'scale' ] = setupGizmo( helperScale ) ); // Pickers should be hidden always
 
-	var pickerTranslate = {
-		X: [
-			[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), matInvisible ), [ 0.6, 0, 0 ], [ 0, 0, - Math.PI / 2 ]]
-		],
-		Y: [
-			[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), matInvisible ), [ 0, 0.6, 0 ]]
-		],
-		Z: [
-			[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 1, 4, 1, false ), matInvisible ), [ 0, 0, 0.6 ], [ Math.PI / 2, 0, 0 ]]
-		],
-		XYZ: [
-			[ new THREE.Mesh( new THREE.OctahedronGeometry( 0.2, 0 ), matInvisible ) ]
-		],
-		XY: [
-			[ new THREE.Mesh( new THREE.PlaneGeometry( 0.4, 0.4 ), matInvisible ), [ 0.2, 0.2, 0 ]]
-		],
-		YZ: [
-			[ new THREE.Mesh( new THREE.PlaneGeometry( 0.4, 0.4 ), matInvisible ), [ 0, 0.2, 0.2 ], [ 0, Math.PI / 2, 0 ]]
-		],
-		XZ: [
-			[ new THREE.Mesh( new THREE.PlaneGeometry( 0.4, 0.4 ), matInvisible ), [ 0.2, 0, 0.2 ], [ - Math.PI / 2, 0, 0 ]]
-		]
-	};
+		this.picker[ 'translate' ].visible = false;
+		this.picker[ 'rotate' ].visible = false;
+		this.picker[ 'scale' ].visible = false; // updateMatrixWorld will update transformations and appearance of individual handles
 
-	var helperTranslate = {
-		START: [
-			[ new THREE.Mesh( new THREE.OctahedronGeometry( 0.01, 2 ), matHelper ), null, null, null, 'helper' ]
-		],
-		END: [
-			[ new THREE.Mesh( new THREE.OctahedronGeometry( 0.01, 2 ), matHelper ), null, null, null, 'helper' ]
-		],
-		DELTA: [
-			[ new THREE.Line( TranslateHelperGeometry(), matHelper ), null, null, null, 'helper' ]
-		],
-		X: [
-			[ new THREE.Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]
-		],
-		Y: [
-			[ new THREE.Line( lineGeometry, matHelper.clone() ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ]
-		],
-		Z: [
-			[ new THREE.Line( lineGeometry, matHelper.clone() ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ]
-		]
-	};
+		this.updateMatrixWorld = function () {
 
-	var gizmoRotate = {
-		X: [
-			[ new THREE.Line( CircleGeometry( 1, 0.5 ), matLineRed ) ],
-			[ new THREE.Mesh( new THREE.OctahedronGeometry( 0.04, 0 ), matRed ), [ 0, 0, 0.99 ], null, [ 1, 3, 1 ]],
-		],
-		Y: [
-			[ new THREE.Line( CircleGeometry( 1, 0.5 ), matLineGreen ), null, [ 0, 0, - Math.PI / 2 ]],
-			[ new THREE.Mesh( new THREE.OctahedronGeometry( 0.04, 0 ), matGreen ), [ 0, 0, 0.99 ], null, [ 3, 1, 1 ]],
-		],
-		Z: [
-			[ new THREE.Line( CircleGeometry( 1, 0.5 ), matLineBlue ), null, [ 0, Math.PI / 2, 0 ]],
-			[ new THREE.Mesh( new THREE.OctahedronGeometry( 0.04, 0 ), matBlue ), [ 0.99, 0, 0 ], null, [ 1, 3, 1 ]],
-		],
-		E: [
-			[ new THREE.Line( CircleGeometry( 1.25, 1 ), matLineYellowTransparent ), null, [ 0, Math.PI / 2, 0 ]],
-			[ new THREE.Mesh( new THREE.CylinderGeometry( 0.03, 0, 0.15, 4, 1, false ), matLineYellowTransparent ), [ 1.17, 0, 0 ], [ 0, 0, - Math.PI / 2 ], [ 1, 1, 0.001 ]],
-			[ new THREE.Mesh( new THREE.CylinderGeometry( 0.03, 0, 0.15, 4, 1, false ), matLineYellowTransparent ), [ - 1.17, 0, 0 ], [ 0, 0, Math.PI / 2 ], [ 1, 1, 0.001 ]],
-			[ new THREE.Mesh( new THREE.CylinderGeometry( 0.03, 0, 0.15, 4, 1, false ), matLineYellowTransparent ), [ 0, - 1.17, 0 ], [ Math.PI, 0, 0 ], [ 1, 1, 0.001 ]],
-			[ new THREE.Mesh( new THREE.CylinderGeometry( 0.03, 0, 0.15, 4, 1, false ), matLineYellowTransparent ), [ 0, 1.17, 0 ], [ 0, 0, 0 ], [ 1, 1, 0.001 ]],
-		],
-		XYZE: [
-			[ new THREE.Line( CircleGeometry( 1, 1 ), matLineGray ), null, [ 0, Math.PI / 2, 0 ]]
-		]
-	};
+			var space = this.space;
+			if ( this.mode === 'scale' ) space = 'local'; // scale always oriented to local rotation
 
-	var helperRotate = {
-		AXIS: [
-			[ new THREE.Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]
-		]
-	};
+			var quaternion = space === 'local' ? this.worldQuaternion : identityQuaternion; // Show only gizmos for current transform mode
 
-	var pickerRotate = {
-		X: [
-			[ new THREE.Mesh( new THREE.TorusGeometry( 1, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, - Math.PI / 2, - Math.PI / 2 ]],
-		],
-		Y: [
-			[ new THREE.Mesh( new THREE.TorusGeometry( 1, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ]],
-		],
-		Z: [
-			[ new THREE.Mesh( new THREE.TorusGeometry( 1, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]],
-		],
-		E: [
-			[ new THREE.Mesh( new THREE.TorusGeometry( 1.25, 0.1, 2, 24 ), matInvisible ) ]
-		],
-		XYZE: [
-			[ new THREE.Mesh( new THREE.SphereGeometry( 0.7, 10, 8 ), matInvisible ) ]
-		]
-	};
+			this.gizmo[ 'translate' ].visible = this.mode === 'translate';
+			this.gizmo[ 'rotate' ].visible = this.mode === 'rotate';
+			this.gizmo[ 'scale' ].visible = this.mode === 'scale';
+			this.helper[ 'translate' ].visible = this.mode === 'translate';
+			this.helper[ 'rotate' ].visible = this.mode === 'rotate';
+			this.helper[ 'scale' ].visible = this.mode === 'scale';
+			var handles = [];
+			handles = handles.concat( this.picker[ this.mode ].children );
+			handles = handles.concat( this.gizmo[ this.mode ].children );
+			handles = handles.concat( this.helper[ this.mode ].children );
 
-	var gizmoScale = {
-		X: [
-			[ new THREE.Mesh( scaleHandleGeometry, matRed ), [ 0.8, 0, 0 ], [ 0, 0, - Math.PI / 2 ]],
-			[ new THREE.Line( lineGeometry, matLineRed ), null, null, [ 0.8, 1, 1 ]]
-		],
-		Y: [
-			[ new THREE.Mesh( scaleHandleGeometry, matGreen ), [ 0, 0.8, 0 ]],
-			[ new THREE.Line( lineGeometry, matLineGreen ), null, [ 0, 0, Math.PI / 2 ], [ 0.8, 1, 1 ]]
-		],
-		Z: [
-			[ new THREE.Mesh( scaleHandleGeometry, matBlue ), [ 0, 0, 0.8 ], [ Math.PI / 2, 0, 0 ]],
-			[ new THREE.Line( lineGeometry, matLineBlue ), null, [ 0, - Math.PI / 2, 0 ], [ 0.8, 1, 1 ]]
-		],
-		XY: [
-			[ new THREE.Mesh( scaleHandleGeometry, matYellowTransparent ), [ 0.85, 0.85, 0 ], null, [ 2, 2, 0.2 ]],
-			[ new THREE.Line( lineGeometry, matLineYellow ), [ 0.855, 0.98, 0 ], null, [ 0.125, 1, 1 ]],
-			[ new THREE.Line( lineGeometry, matLineYellow ), [ 0.98, 0.855, 0 ], [ 0, 0, Math.PI / 2 ], [ 0.125, 1, 1 ]]
-		],
-		YZ: [
-			[ new THREE.Mesh( scaleHandleGeometry, matCyanTransparent ), [ 0, 0.85, 0.85 ], null, [ 0.2, 2, 2 ]],
-			[ new THREE.Line( lineGeometry, matLineCyan ), [ 0, 0.855, 0.98 ], [ 0, 0, Math.PI / 2 ], [ 0.125, 1, 1 ]],
-			[ new THREE.Line( lineGeometry, matLineCyan ), [ 0, 0.98, 0.855 ], [ 0, - Math.PI / 2, 0 ], [ 0.125, 1, 1 ]]
-		],
-		XZ: [
-			[ new THREE.Mesh( scaleHandleGeometry, matMagentaTransparent ), [ 0.85, 0, 0.85 ], null, [ 2, 0.2, 2 ]],
-			[ new THREE.Line( lineGeometry, matLineMagenta ), [ 0.855, 0, 0.98 ], null, [ 0.125, 1, 1 ]],
-			[ new THREE.Line( lineGeometry, matLineMagenta ), [ 0.98, 0, 0.855 ], [ 0, - Math.PI / 2, 0 ], [ 0.125, 1, 1 ]]
-		],
-		XYZX: [
-			[ new THREE.Mesh( new THREE.BoxGeometry( 0.125, 0.125, 0.125 ), matWhiteTransparent.clone() ), [ 1.1, 0, 0 ]],
-		],
-		XYZY: [
-			[ new THREE.Mesh( new THREE.BoxGeometry( 0.125, 0.125, 0.125 ), matWhiteTransparent.clone() ), [ 0, 1.1, 0 ]],
-		],
-		XYZZ: [
-			[ new THREE.Mesh( new THREE.BoxGeometry( 0.125, 0.125, 0.125 ), matWhiteTransparent.clone() ), [ 0, 0, 1.1 ]],
-		]
-	};
+			for ( var i = 0; i < handles.length; i ++ ) {
 
-	var pickerScale = {
-		X: [
-			[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 0.8, 4, 1, false ), matInvisible ), [ 0.5, 0, 0 ], [ 0, 0, - Math.PI / 2 ]]
-		],
-		Y: [
-			[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 0.8, 4, 1, false ), matInvisible ), [ 0, 0.5, 0 ]]
-		],
-		Z: [
-			[ new THREE.Mesh( new THREE.CylinderGeometry( 0.2, 0, 0.8, 4, 1, false ), matInvisible ), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ]]
-		],
-		XY: [
-			[ new THREE.Mesh( scaleHandleGeometry, matInvisible ), [ 0.85, 0.85, 0 ], null, [ 3, 3, 0.2 ]],
-		],
-		YZ: [
-			[ new THREE.Mesh( scaleHandleGeometry, matInvisible ), [ 0, 0.85, 0.85 ], null, [ 0.2, 3, 3 ]],
-		],
-		XZ: [
-			[ new THREE.Mesh( scaleHandleGeometry, matInvisible ), [ 0.85, 0, 0.85 ], null, [ 3, 0.2, 3 ]],
-		],
-		XYZX: [
-			[ new THREE.Mesh( new THREE.BoxGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 1.1, 0, 0 ]],
-		],
-		XYZY: [
-			[ new THREE.Mesh( new THREE.BoxGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 0, 1.1, 0 ]],
-		],
-		XYZZ: [
-			[ new THREE.Mesh( new THREE.BoxGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 0, 0, 1.1 ]],
-		]
-	};
+				var handle = handles[ i ]; // hide aligned to camera
 
-	var helperScale = {
-		X: [
-			[ new THREE.Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]
-		],
-		Y: [
-			[ new THREE.Line( lineGeometry, matHelper.clone() ), [ 0, - 1e3, 0 ], [ 0, 0, Math.PI / 2 ], [ 1e6, 1, 1 ], 'helper' ]
-		],
-		Z: [
-			[ new THREE.Line( lineGeometry, matHelper.clone() ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ]
-		]
-	};
+				handle.visible = true;
+				handle.rotation.set( 0, 0, 0 );
+				handle.position.copy( this.worldPosition );
+				var factor;
 
-	// Creates an Object3D with gizmos described in custom hierarchy definition.
+				if ( this.camera.isOrthographicCamera ) {
 
-	var setupGizmo = function ( gizmoMap ) {
+					factor = ( this.camera.top - this.camera.bottom ) / this.camera.zoom;
 
-		var gizmo = new THREE.Object3D();
+				} else {
 
-		for ( var name in gizmoMap ) {
+					factor = this.worldPosition.distanceTo( this.cameraPosition ) * Math.min( 1.9 * Math.tan( Math.PI * this.camera.fov / 360 ) / this.camera.zoom, 7 );
 
-			for ( var i = gizmoMap[ name ].length; i --; ) {
+				}
 
-				var object = gizmoMap[ name ][ i ][ 0 ].clone();
-				var position = gizmoMap[ name ][ i ][ 1 ];
-				var rotation = gizmoMap[ name ][ i ][ 2 ];
-				var scale = gizmoMap[ name ][ i ][ 3 ];
-				var tag = gizmoMap[ name ][ i ][ 4 ];
+				handle.scale.set( 1, 1, 1 ).multiplyScalar( factor * this.size / 7 ); // TODO: simplify helpers and consider decoupling from gizmo
 
-				// name and tag properties are essential for picking and updating logic.
-				object.name = name;
-				object.tag = tag;
+				if ( handle.tag === 'helper' ) {
 
-				if ( position ) {
+					handle.visible = false;
 
-					object.position.set( position[ 0 ], position[ 1 ], position[ 2 ] );
+					if ( handle.name === 'AXIS' ) {
 
-				}
+						handle.position.copy( this.worldPositionStart );
+						handle.visible = !! this.axis;
 
-				if ( rotation ) {
+						if ( this.axis === 'X' ) {
 
-					object.rotation.set( rotation[ 0 ], rotation[ 1 ], rotation[ 2 ] );
+							tempQuaternion.setFromEuler( tempEuler.set( 0, 0, 0 ) );
+							handle.quaternion.copy( quaternion ).multiply( tempQuaternion );
 
-				}
+							if ( Math.abs( alignVector.copy( unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) {
 
-				if ( scale ) {
+								handle.visible = false;
 
-					object.scale.set( scale[ 0 ], scale[ 1 ], scale[ 2 ] );
+							}
 
-				}
+						}
 
-				object.updateMatrix();
+						if ( this.axis === 'Y' ) {
 
-				var tempGeometry = object.geometry.clone();
-				tempGeometry.applyMatrix4( object.matrix );
-				object.geometry = tempGeometry;
-				object.renderOrder = Infinity;
+							tempQuaternion.setFromEuler( tempEuler.set( 0, 0, Math.PI / 2 ) );
+							handle.quaternion.copy( quaternion ).multiply( tempQuaternion );
 
-				object.position.set( 0, 0, 0 );
-				object.rotation.set( 0, 0, 0 );
-				object.scale.set( 1, 1, 1 );
+							if ( Math.abs( alignVector.copy( unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) {
 
-				gizmo.add( object );
+								handle.visible = false;
 
-			}
+							}
 
-		}
+						}
 
-		return gizmo;
+						if ( this.axis === 'Z' ) {
 
-	};
+							tempQuaternion.setFromEuler( tempEuler.set( 0, Math.PI / 2, 0 ) );
+							handle.quaternion.copy( quaternion ).multiply( tempQuaternion );
 
-	// Reusable utility variables
+							if ( Math.abs( alignVector.copy( unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) {
 
-	var tempVector = new THREE.Vector3( 0, 0, 0 );
-	var tempEuler = new THREE.Euler();
-	var alignVector = new THREE.Vector3( 0, 1, 0 );
-	var zeroVector = new THREE.Vector3( 0, 0, 0 );
-	var lookAtMatrix = new THREE.Matrix4();
-	var tempQuaternion = new THREE.Quaternion();
-	var tempQuaternion2 = new THREE.Quaternion();
-	var identityQuaternion = new THREE.Quaternion();
+								handle.visible = false;
 
-	var unitX = new THREE.Vector3( 1, 0, 0 );
-	var unitY = new THREE.Vector3( 0, 1, 0 );
-	var unitZ = new THREE.Vector3( 0, 0, 1 );
+							}
 
-	// Gizmo creation
+						}
 
-	this.gizmo = {};
-	this.picker = {};
-	this.helper = {};
+						if ( this.axis === 'XYZE' ) {
 
-	this.add( this.gizmo[ 'translate' ] = setupGizmo( gizmoTranslate ) );
-	this.add( this.gizmo[ 'rotate' ] = setupGizmo( gizmoRotate ) );
-	this.add( this.gizmo[ 'scale' ] = setupGizmo( gizmoScale ) );
-	this.add( this.picker[ 'translate' ] = setupGizmo( pickerTranslate ) );
-	this.add( this.picker[ 'rotate' ] = setupGizmo( pickerRotate ) );
-	this.add( this.picker[ 'scale' ] = setupGizmo( pickerScale ) );
-	this.add( this.helper[ 'translate' ] = setupGizmo( helperTranslate ) );
-	this.add( this.helper[ 'rotate' ] = setupGizmo( helperRotate ) );
-	this.add( this.helper[ 'scale' ] = setupGizmo( helperScale ) );
+							tempQuaternion.setFromEuler( tempEuler.set( 0, Math.PI / 2, 0 ) );
+							alignVector.copy( this.rotationAxis );
+							handle.quaternion.setFromRotationMatrix( lookAtMatrix.lookAt( zeroVector, alignVector, unitY ) );
+							handle.quaternion.multiply( tempQuaternion );
+							handle.visible = this.dragging;
 
-	// Pickers should be hidden always
+						}
 
-	this.picker[ 'translate' ].visible = false;
-	this.picker[ 'rotate' ].visible = false;
-	this.picker[ 'scale' ].visible = false;
+						if ( this.axis === 'E' ) {
 
-	// updateMatrixWorld will update transformations and appearance of individual handles
+							handle.visible = false;
 
-	this.updateMatrixWorld = function () {
+						}
 
-		var space = this.space;
+					} else if ( handle.name === 'START' ) {
 
-		if ( this.mode === 'scale' ) space = 'local'; // scale always oriented to local rotation
+						handle.position.copy( this.worldPositionStart );
+						handle.visible = this.dragging;
 
-		var quaternion = space === 'local' ? this.worldQuaternion : identityQuaternion;
+					} else if ( handle.name === 'END' ) {
 
-		// Show only gizmos for current transform mode
+						handle.position.copy( this.worldPosition );
+						handle.visible = this.dragging;
 
-		this.gizmo[ 'translate' ].visible = this.mode === 'translate';
-		this.gizmo[ 'rotate' ].visible = this.mode === 'rotate';
-		this.gizmo[ 'scale' ].visible = this.mode === 'scale';
+					} else if ( handle.name === 'DELTA' ) {
 
-		this.helper[ 'translate' ].visible = this.mode === 'translate';
-		this.helper[ 'rotate' ].visible = this.mode === 'rotate';
-		this.helper[ 'scale' ].visible = this.mode === 'scale';
+						handle.position.copy( this.worldPositionStart );
+						handle.quaternion.copy( this.worldQuaternionStart );
+						tempVector.set( 1e-10, 1e-10, 1e-10 ).add( this.worldPositionStart ).sub( this.worldPosition ).multiplyScalar( - 1 );
+						tempVector.applyQuaternion( this.worldQuaternionStart.clone().invert() );
+						handle.scale.copy( tempVector );
+						handle.visible = this.dragging;
 
+					} else {
 
-		var handles = [];
-		handles = handles.concat( this.picker[ this.mode ].children );
-		handles = handles.concat( this.gizmo[ this.mode ].children );
-		handles = handles.concat( this.helper[ this.mode ].children );
+						handle.quaternion.copy( quaternion );
 
-		for ( var i = 0; i < handles.length; i ++ ) {
+						if ( this.dragging ) {
 
-			var handle = handles[ i ];
+							handle.position.copy( this.worldPositionStart );
 
-			// hide aligned to camera
+						} else {
 
-			handle.visible = true;
-			handle.rotation.set( 0, 0, 0 );
-			handle.position.copy( this.worldPosition );
+							handle.position.copy( this.worldPosition );
 
-			var factor;
+						}
 
-			if ( this.camera.isOrthographicCamera ) {
+						if ( this.axis ) {
 
-				factor = ( this.camera.top - this.camera.bottom ) / this.camera.zoom;
+							handle.visible = this.axis.search( handle.name ) !== - 1;
 
-			} else {
+						}
 
-				factor = this.worldPosition.distanceTo( this.cameraPosition ) * Math.min( 1.9 * Math.tan( Math.PI * this.camera.fov / 360 ) / this.camera.zoom, 7 );
+					} // If updating helper, skip rest of the loop
 
-			}
 
-			handle.scale.set( 1, 1, 1 ).multiplyScalar( factor * this.size / 7 );
+					continue;
 
-			// TODO: simplify helpers and consider decoupling from gizmo
+				} // Align handles to current local or world rotation
 
-			if ( handle.tag === 'helper' ) {
 
-				handle.visible = false;
+				handle.quaternion.copy( quaternion );
 
-				if ( handle.name === 'AXIS' ) {
+				if ( this.mode === 'translate' || this.mode === 'scale' ) {
 
-					handle.position.copy( this.worldPositionStart );
-					handle.visible = !! this.axis;
+					// Hide translate and scale axis facing the camera
+					var AXIS_HIDE_TRESHOLD = 0.99;
+					var PLANE_HIDE_TRESHOLD = 0.2;
+					var AXIS_FLIP_TRESHOLD = 0.0;
 
-					if ( this.axis === 'X' ) {
+					if ( handle.name === 'X' || handle.name === 'XYZX' ) {
 
-						tempQuaternion.setFromEuler( tempEuler.set( 0, 0, 0 ) );
-						handle.quaternion.copy( quaternion ).multiply( tempQuaternion );
-
-						if ( Math.abs( alignVector.copy( unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) {
+						if ( Math.abs( alignVector.copy( unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) {
 
+							handle.scale.set( 1e-10, 1e-10, 1e-10 );
 							handle.visible = false;
 
 						}
 
 					}
 
-					if ( this.axis === 'Y' ) {
-
-						tempQuaternion.setFromEuler( tempEuler.set( 0, 0, Math.PI / 2 ) );
-						handle.quaternion.copy( quaternion ).multiply( tempQuaternion );
+					if ( handle.name === 'Y' || handle.name === 'XYZY' ) {
 
-						if ( Math.abs( alignVector.copy( unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) {
+						if ( Math.abs( alignVector.copy( unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) {
 
+							handle.scale.set( 1e-10, 1e-10, 1e-10 );
 							handle.visible = false;
 
 						}
 
 					}
 
-					if ( this.axis === 'Z' ) {
-
-						tempQuaternion.setFromEuler( tempEuler.set( 0, Math.PI / 2, 0 ) );
-						handle.quaternion.copy( quaternion ).multiply( tempQuaternion );
+					if ( handle.name === 'Z' || handle.name === 'XYZZ' ) {
 
-						if ( Math.abs( alignVector.copy( unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) {
+						if ( Math.abs( alignVector.copy( unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) {
 
+							handle.scale.set( 1e-10, 1e-10, 1e-10 );
 							handle.visible = false;
 
 						}
 
 					}
 
-					if ( this.axis === 'XYZE' ) {
-
-						tempQuaternion.setFromEuler( tempEuler.set( 0, Math.PI / 2, 0 ) );
-						alignVector.copy( this.rotationAxis );
-						handle.quaternion.setFromRotationMatrix( lookAtMatrix.lookAt( zeroVector, alignVector, unitY ) );
-						handle.quaternion.multiply( tempQuaternion );
-						handle.visible = this.dragging;
-
-					}
-
-					if ( this.axis === 'E' ) {
-
-						handle.visible = false;
-
-					}
-
+					if ( handle.name === 'XY' ) {
 
-				} else if ( handle.name === 'START' ) {
-
-					handle.position.copy( this.worldPositionStart );
-					handle.visible = this.dragging;
-
-				} else if ( handle.name === 'END' ) {
-
-					handle.position.copy( this.worldPosition );
-					handle.visible = this.dragging;
-
-				} else if ( handle.name === 'DELTA' ) {
-
-					handle.position.copy( this.worldPositionStart );
-					handle.quaternion.copy( this.worldQuaternionStart );
-					tempVector.set( 1e-10, 1e-10, 1e-10 ).add( this.worldPositionStart ).sub( this.worldPosition ).multiplyScalar( - 1 );
-					tempVector.applyQuaternion( this.worldQuaternionStart.clone().invert() );
-					handle.scale.copy( tempVector );
-					handle.visible = this.dragging;
-
-				} else {
-
-					handle.quaternion.copy( quaternion );
-
-					if ( this.dragging ) {
-
-						handle.position.copy( this.worldPositionStart );
+						if ( Math.abs( alignVector.copy( unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_TRESHOLD ) {
 
-					} else {
-
-						handle.position.copy( this.worldPosition );
-
-					}
-
-					if ( this.axis ) {
+							handle.scale.set( 1e-10, 1e-10, 1e-10 );
+							handle.visible = false;
 
-						handle.visible = this.axis.search( handle.name ) !== - 1;
+						}
 
 					}
 
-				}
-
-				// If updating helper, skip rest of the loop
-				continue;
-
-			}
-
-			// Align handles to current local or world rotation
-
-			handle.quaternion.copy( quaternion );
-
-			if ( this.mode === 'translate' || this.mode === 'scale' ) {
-
-				// Hide translate and scale axis facing the camera
-
-				var AXIS_HIDE_TRESHOLD = 0.99;
-				var PLANE_HIDE_TRESHOLD = 0.2;
-				var AXIS_FLIP_TRESHOLD = 0.0;
+					if ( handle.name === 'YZ' ) {
 
+						if ( Math.abs( alignVector.copy( unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_TRESHOLD ) {
 
-				if ( handle.name === 'X' || handle.name === 'XYZX' ) {
-
-					if ( Math.abs( alignVector.copy( unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) {
+							handle.scale.set( 1e-10, 1e-10, 1e-10 );
+							handle.visible = false;
 
-						handle.scale.set( 1e-10, 1e-10, 1e-10 );
-						handle.visible = false;
+						}
 
 					}
 
-				}
-
-				if ( handle.name === 'Y' || handle.name === 'XYZY' ) {
+					if ( handle.name === 'XZ' ) {
 
-					if ( Math.abs( alignVector.copy( unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) {
+						if ( Math.abs( alignVector.copy( unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_TRESHOLD ) {
 
-						handle.scale.set( 1e-10, 1e-10, 1e-10 );
-						handle.visible = false;
-
-					}
-
-				}
+							handle.scale.set( 1e-10, 1e-10, 1e-10 );
+							handle.visible = false;
 
-				if ( handle.name === 'Z' || handle.name === 'XYZZ' ) {
+						}
 
-					if ( Math.abs( alignVector.copy( unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) {
+					} // Flip translate and scale axis ocluded behind another axis
 
-						handle.scale.set( 1e-10, 1e-10, 1e-10 );
-						handle.visible = false;
 
-					}
+					if ( handle.name.search( 'X' ) !== - 1 ) {
 
-				}
+						if ( alignVector.copy( unitX ).applyQuaternion( quaternion ).dot( this.eye ) < AXIS_FLIP_TRESHOLD ) {
 
-				if ( handle.name === 'XY' ) {
+							if ( handle.tag === 'fwd' ) {
 
-					if ( Math.abs( alignVector.copy( unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_TRESHOLD ) {
+								handle.visible = false;
 
-						handle.scale.set( 1e-10, 1e-10, 1e-10 );
-						handle.visible = false;
+							} else {
 
-					}
+								handle.scale.x *= - 1;
 
-				}
+							}
 
-				if ( handle.name === 'YZ' ) {
+						} else if ( handle.tag === 'bwd' ) {
 
-					if ( Math.abs( alignVector.copy( unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_TRESHOLD ) {
+							handle.visible = false;
 
-						handle.scale.set( 1e-10, 1e-10, 1e-10 );
-						handle.visible = false;
+						}
 
 					}
 
-				}
-
-				if ( handle.name === 'XZ' ) {
-
-					if ( Math.abs( alignVector.copy( unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_TRESHOLD ) {
+					if ( handle.name.search( 'Y' ) !== - 1 ) {
 
-						handle.scale.set( 1e-10, 1e-10, 1e-10 );
-						handle.visible = false;
+						if ( alignVector.copy( unitY ).applyQuaternion( quaternion ).dot( this.eye ) < AXIS_FLIP_TRESHOLD ) {
 
-					}
+							if ( handle.tag === 'fwd' ) {
 
-				}
+								handle.visible = false;
 
-				// Flip translate and scale axis ocluded behind another axis
+							} else {
 
-				if ( handle.name.search( 'X' ) !== - 1 ) {
+								handle.scale.y *= - 1;
 
-					if ( alignVector.copy( unitX ).applyQuaternion( quaternion ).dot( this.eye ) < AXIS_FLIP_TRESHOLD ) {
+							}
 
-						if ( handle.tag === 'fwd' ) {
+						} else if ( handle.tag === 'bwd' ) {
 
 							handle.visible = false;
 
-						} else {
-
-							handle.scale.x *= - 1;
-
 						}
 
-					} else if ( handle.tag === 'bwd' ) {
-
-						handle.visible = false;
-
 					}
 
-				}
+					if ( handle.name.search( 'Z' ) !== - 1 ) {
 
-				if ( handle.name.search( 'Y' ) !== - 1 ) {
+						if ( alignVector.copy( unitZ ).applyQuaternion( quaternion ).dot( this.eye ) < AXIS_FLIP_TRESHOLD ) {
 
-					if ( alignVector.copy( unitY ).applyQuaternion( quaternion ).dot( this.eye ) < AXIS_FLIP_TRESHOLD ) {
+							if ( handle.tag === 'fwd' ) {
 
-						if ( handle.tag === 'fwd' ) {
+								handle.visible = false;
 
-							handle.visible = false;
-
-						} else {
-
-							handle.scale.y *= - 1;
-
-						}
-
-					} else if ( handle.tag === 'bwd' ) {
-
-						handle.visible = false;
-
-					}
-
-				}
+							} else {
 
-				if ( handle.name.search( 'Z' ) !== - 1 ) {
+								handle.scale.z *= - 1;
 
-					if ( alignVector.copy( unitZ ).applyQuaternion( quaternion ).dot( this.eye ) < AXIS_FLIP_TRESHOLD ) {
+							}
 
-						if ( handle.tag === 'fwd' ) {
+						} else if ( handle.tag === 'bwd' ) {
 
 							handle.visible = false;
 
-						} else {
-
-							handle.scale.z *= - 1;
-
 						}
 
-					} else if ( handle.tag === 'bwd' ) {
-
-						handle.visible = false;
-
 					}
 
-				}
-
-			} else if ( this.mode === 'rotate' ) {
-
-				// Align handles to current local or world rotation
+				} else if ( this.mode === 'rotate' ) {
 
-				tempQuaternion2.copy( quaternion );
-				alignVector.copy( this.eye ).applyQuaternion( tempQuaternion.copy( quaternion ).invert() );
+					// Align handles to current local or world rotation
+					tempQuaternion2.copy( quaternion );
+					alignVector.copy( this.eye ).applyQuaternion( tempQuaternion.copy( quaternion ).invert() );
 
-				if ( handle.name.search( 'E' ) !== - 1 ) {
+					if ( handle.name.search( 'E' ) !== - 1 ) {
 
-					handle.quaternion.setFromRotationMatrix( lookAtMatrix.lookAt( this.eye, zeroVector, unitY ) );
+						handle.quaternion.setFromRotationMatrix( lookAtMatrix.lookAt( this.eye, zeroVector, unitY ) );
 
-				}
+					}
 
-				if ( handle.name === 'X' ) {
+					if ( handle.name === 'X' ) {
 
-					tempQuaternion.setFromAxisAngle( unitX, Math.atan2( - alignVector.y, alignVector.z ) );
-					tempQuaternion.multiplyQuaternions( tempQuaternion2, tempQuaternion );
-					handle.quaternion.copy( tempQuaternion );
+						tempQuaternion.setFromAxisAngle( unitX, Math.atan2( - alignVector.y, alignVector.z ) );
+						tempQuaternion.multiplyQuaternions( tempQuaternion2, tempQuaternion );
+						handle.quaternion.copy( tempQuaternion );
 
-				}
+					}
 
-				if ( handle.name === 'Y' ) {
+					if ( handle.name === 'Y' ) {
 
-					tempQuaternion.setFromAxisAngle( unitY, Math.atan2( alignVector.x, alignVector.z ) );
-					tempQuaternion.multiplyQuaternions( tempQuaternion2, tempQuaternion );
-					handle.quaternion.copy( tempQuaternion );
+						tempQuaternion.setFromAxisAngle( unitY, Math.atan2( alignVector.x, alignVector.z ) );
+						tempQuaternion.multiplyQuaternions( tempQuaternion2, tempQuaternion );
+						handle.quaternion.copy( tempQuaternion );
 
-				}
+					}
 
-				if ( handle.name === 'Z' ) {
+					if ( handle.name === 'Z' ) {
 
-					tempQuaternion.setFromAxisAngle( unitZ, Math.atan2( alignVector.y, alignVector.x ) );
-					tempQuaternion.multiplyQuaternions( tempQuaternion2, tempQuaternion );
-					handle.quaternion.copy( tempQuaternion );
+						tempQuaternion.setFromAxisAngle( unitZ, Math.atan2( alignVector.y, alignVector.x ) );
+						tempQuaternion.multiplyQuaternions( tempQuaternion2, tempQuaternion );
+						handle.quaternion.copy( tempQuaternion );
 
-				}
+					}
 
-			}
+				} // Hide disabled axes
 
-			// Hide disabled axes
-			handle.visible = handle.visible && ( handle.name.indexOf( 'X' ) === - 1 || this.showX );
-			handle.visible = handle.visible && ( handle.name.indexOf( 'Y' ) === - 1 || this.showY );
-			handle.visible = handle.visible && ( handle.name.indexOf( 'Z' ) === - 1 || this.showZ );
-			handle.visible = handle.visible && ( handle.name.indexOf( 'E' ) === - 1 || ( this.showX && this.showY && this.showZ ) );
 
-			// highlight selected axis
+				handle.visible = handle.visible && ( handle.name.indexOf( 'X' ) === - 1 || this.showX );
+				handle.visible = handle.visible && ( handle.name.indexOf( 'Y' ) === - 1 || this.showY );
+				handle.visible = handle.visible && ( handle.name.indexOf( 'Z' ) === - 1 || this.showZ );
+				handle.visible = handle.visible && ( handle.name.indexOf( 'E' ) === - 1 || this.showX && this.showY && this.showZ ); // highlight selected axis
 
-			handle.material._opacity = handle.material._opacity || handle.material.opacity;
-			handle.material._color = handle.material._color || handle.material.color.clone();
+				handle.material._opacity = handle.material._opacity || handle.material.opacity;
+				handle.material._color = handle.material._color || handle.material.color.clone();
+				handle.material.color.copy( handle.material._color );
+				handle.material.opacity = handle.material._opacity;
 
-			handle.material.color.copy( handle.material._color );
-			handle.material.opacity = handle.material._opacity;
+				if ( ! this.enabled ) {
 
-			if ( ! this.enabled ) {
+					handle.material.opacity *= 0.5;
+					handle.material.color.lerp( new THREE.Color( 1, 1, 1 ), 0.5 );
 
-				handle.material.opacity *= 0.5;
-				handle.material.color.lerp( new THREE.Color( 1, 1, 1 ), 0.5 );
+				} else if ( this.axis ) {
 
-			} else if ( this.axis ) {
+					if ( handle.name === this.axis ) {
 
-				if ( handle.name === this.axis ) {
+						handle.material.opacity = 1.0;
+						handle.material.color.lerp( new THREE.Color( 1, 1, 1 ), 0.5 );
 
-					handle.material.opacity = 1.0;
-					handle.material.color.lerp( new THREE.Color( 1, 1, 1 ), 0.5 );
+					} else if ( this.axis.split( '' ).some( function ( a ) {
 
-				} else if ( this.axis.split( '' ).some( function ( a ) {
+						return handle.name === a;
 
-					return handle.name === a;
+					} ) ) {
 
-				} ) ) {
+						handle.material.opacity = 1.0;
+						handle.material.color.lerp( new THREE.Color( 1, 1, 1 ), 0.5 );
 
-					handle.material.opacity = 1.0;
-					handle.material.color.lerp( new THREE.Color( 1, 1, 1 ), 0.5 );
+					} else {
 
-				} else {
+						handle.material.opacity *= 0.25;
+						handle.material.color.lerp( new THREE.Color( 1, 1, 1 ), 0.5 );
 
-					handle.material.opacity *= 0.25;
-					handle.material.color.lerp( new THREE.Color( 1, 1, 1 ), 0.5 );
+					}
 
 				}
 
 			}
 
-		}
+			THREE.Object3D.prototype.updateMatrixWorld.call( this );
 
-		THREE.Object3D.prototype.updateMatrixWorld.call( this );
+		};
 
 	};
 
-};
-
-THREE.TransformControlsGizmo.prototype = Object.assign( Object.create( THREE.Object3D.prototype ), {
-
-	constructor: THREE.TransformControlsGizmo,
-
-	isTransformControlsGizmo: true
-
-} );
-
-
-THREE.TransformControlsPlane = function () {
-
-	'use strict';
-
-	THREE.Mesh.call( this,
-		new THREE.PlaneGeometry( 100000, 100000, 2, 2 ),
-		new THREE.MeshBasicMaterial( { visible: false, wireframe: true, side: THREE.DoubleSide, transparent: true, opacity: 0.1, toneMapped: false } )
-	);
-
-	this.type = 'TransformControlsPlane';
-
-	var unitX = new THREE.Vector3( 1, 0, 0 );
-	var unitY = new THREE.Vector3( 0, 1, 0 );
-	var unitZ = new THREE.Vector3( 0, 0, 1 );
-
-	var tempVector = new THREE.Vector3();
-	var dirVector = new THREE.Vector3();
-	var alignVector = new THREE.Vector3();
-	var tempMatrix = new THREE.Matrix4();
-	var identityQuaternion = new THREE.Quaternion();
-
-	this.updateMatrixWorld = function () {
-
-		var space = this.space;
-
-		this.position.copy( this.worldPosition );
-
-		if ( this.mode === 'scale' ) space = 'local'; // scale always oriented to local rotation
-
-		unitX.set( 1, 0, 0 ).applyQuaternion( space === 'local' ? this.worldQuaternion : identityQuaternion );
-		unitY.set( 0, 1, 0 ).applyQuaternion( space === 'local' ? this.worldQuaternion : identityQuaternion );
-		unitZ.set( 0, 0, 1 ).applyQuaternion( space === 'local' ? this.worldQuaternion : identityQuaternion );
-
-		// Align the plane for current transform mode, axis and space.
-
-		alignVector.copy( unitY );
-
-		switch ( this.mode ) {
+	TransformControlsGizmo.prototype = Object.assign( Object.create( THREE.Object3D.prototype ), {
+		constructor: TransformControlsGizmo,
+		isTransformControlsGizmo: true
+	} );
 
-			case 'translate':
-			case 'scale':
-				switch ( this.axis ) {
+	var TransformControlsPlane = function () {
+
+		'use strict';
+
+		THREE.Mesh.call( this, new THREE.PlaneGeometry( 100000, 100000, 2, 2 ), new THREE.MeshBasicMaterial( {
+			visible: false,
+			wireframe: true,
+			side: THREE.DoubleSide,
+			transparent: true,
+			opacity: 0.1,
+			toneMapped: false
+		} ) );
+		this.type = 'TransformControlsPlane';
+		var unitX = new THREE.Vector3( 1, 0, 0 );
+		var unitY = new THREE.Vector3( 0, 1, 0 );
+		var unitZ = new THREE.Vector3( 0, 0, 1 );
+		var tempVector = new THREE.Vector3();
+		var dirVector = new THREE.Vector3();
+		var alignVector = new THREE.Vector3();
+		var tempMatrix = new THREE.Matrix4();
+		var identityQuaternion = new THREE.Quaternion();
+
+		this.updateMatrixWorld = function () {
+
+			var space = this.space;
+			this.position.copy( this.worldPosition );
+			if ( this.mode === 'scale' ) space = 'local'; // scale always oriented to local rotation
+
+			unitX.set( 1, 0, 0 ).applyQuaternion( space === 'local' ? this.worldQuaternion : identityQuaternion );
+			unitY.set( 0, 1, 0 ).applyQuaternion( space === 'local' ? this.worldQuaternion : identityQuaternion );
+			unitZ.set( 0, 0, 1 ).applyQuaternion( space === 'local' ? this.worldQuaternion : identityQuaternion ); // Align the plane for current transform mode, axis and space.
+
+			alignVector.copy( unitY );
+
+			switch ( this.mode ) {
+
+				case 'translate':
+				case 'scale':
+					switch ( this.axis ) {
+
+						case 'X':
+							alignVector.copy( this.eye ).cross( unitX );
+							dirVector.copy( unitX ).cross( alignVector );
+							break;
+
+						case 'Y':
+							alignVector.copy( this.eye ).cross( unitY );
+							dirVector.copy( unitY ).cross( alignVector );
+							break;
+
+						case 'Z':
+							alignVector.copy( this.eye ).cross( unitZ );
+							dirVector.copy( unitZ ).cross( alignVector );
+							break;
+
+						case 'XY':
+							dirVector.copy( unitZ );
+							break;
+
+						case 'YZ':
+							dirVector.copy( unitX );
+							break;
+
+						case 'XZ':
+							alignVector.copy( unitZ );
+							dirVector.copy( unitY );
+							break;
+
+						case 'XYZ':
+						case 'E':
+							dirVector.set( 0, 0, 0 );
+							break;
 
-					case 'X':
-						alignVector.copy( this.eye ).cross( unitX );
-						dirVector.copy( unitX ).cross( alignVector );
-						break;
-					case 'Y':
-						alignVector.copy( this.eye ).cross( unitY );
-						dirVector.copy( unitY ).cross( alignVector );
-						break;
-					case 'Z':
-						alignVector.copy( this.eye ).cross( unitZ );
-						dirVector.copy( unitZ ).cross( alignVector );
-						break;
-					case 'XY':
-						dirVector.copy( unitZ );
-						break;
-					case 'YZ':
-						dirVector.copy( unitX );
-						break;
-					case 'XZ':
-						alignVector.copy( unitZ );
-						dirVector.copy( unitY );
-						break;
-					case 'XYZ':
-					case 'E':
-						dirVector.set( 0, 0, 0 );
-						break;
+					}
 
-				}
+					break;
 
-				break;
-			case 'rotate':
-			default:
+				case 'rotate':
+				default:
 				// special case for rotate
-				dirVector.set( 0, 0, 0 );
+					dirVector.set( 0, 0, 0 );
 
-		}
+			}
 
-		if ( dirVector.length() === 0 ) {
+			if ( dirVector.length() === 0 ) {
 
-			// If in rotate mode, make the plane parallel to camera
-			this.quaternion.copy( this.cameraQuaternion );
+				// If in rotate mode, make the plane parallel to camera
+				this.quaternion.copy( this.cameraQuaternion );
 
-		} else {
+			} else {
 
-			tempMatrix.lookAt( tempVector.set( 0, 0, 0 ), dirVector, alignVector );
+				tempMatrix.lookAt( tempVector.set( 0, 0, 0 ), dirVector, alignVector );
+				this.quaternion.setFromRotationMatrix( tempMatrix );
 
-			this.quaternion.setFromRotationMatrix( tempMatrix );
+			}
 
-		}
+			THREE.Object3D.prototype.updateMatrixWorld.call( this );
 
-		THREE.Object3D.prototype.updateMatrixWorld.call( this );
+		};
 
 	};
 
-};
-
-THREE.TransformControlsPlane.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), {
-
-	constructor: THREE.TransformControlsPlane,
+	TransformControlsPlane.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), {
+		constructor: TransformControlsPlane,
+		isTransformControlsPlane: true
+	} );
 
-	isTransformControlsPlane: true
+	THREE.TransformControls = TransformControls;
+	THREE.TransformControlsGizmo = TransformControlsGizmo;
+	THREE.TransformControlsPlane = TransformControlsPlane;
 
-} );
+} )();

+ 1044 - 0
examples/js/controls/experimental/CameraControls.js

@@ -0,0 +1,1044 @@
+( function () {
+
+	var CameraControls = function ( object, domElement ) {
+
+		if ( domElement === undefined ) console.warn( 'THREE.CameraControls: The second parameter "domElement" is now mandatory.' );
+		if ( domElement === document ) console.error( 'THREE.CameraControls: "document" should not be used as the target "domElement". Please use "renderer.domElement" instead.' );
+		this.object = object;
+		this.domElement = domElement; // Set to false to disable this control
+
+		this.enabled = true; // "target" sets the location of focus, where the object orbits around
+
+		this.target = new THREE.Vector3(); // Set to true to enable trackball behavior
+
+		this.trackball = false; // How far you can dolly in and out ( PerspectiveCamera only )
+
+		this.minDistance = 0;
+		this.maxDistance = Infinity; // How far you can zoom in and out ( OrthographicCamera only )
+
+		this.minZoom = 0;
+		this.maxZoom = Infinity; // How far you can orbit vertically, upper and lower limits.
+		// Range is 0 to Math.PI radians.
+
+		this.minPolarAngle = 0; // radians
+
+		this.maxPolarAngle = Math.PI; // radians
+		// How far you can orbit horizontally, upper and lower limits.
+		// If set, must be a sub-interval of the interval [ - Math.PI, Math.PI ].
+
+		this.minAzimuthAngle = - Infinity; // radians
+
+		this.maxAzimuthAngle = Infinity; // radians
+		// Set to true to enable damping (inertia)
+		// If damping is enabled, you must call controls.update() in your animation loop
+
+		this.enableDamping = false;
+		this.dampingFactor = 0.05; // This option enables dollying in and out; property named as "zoom" for backwards compatibility
+		// Set to false to disable zooming
+
+		this.enableZoom = true;
+		this.zoomSpeed = 1.0; // Set to false to disable rotating
+
+		this.enableRotate = true;
+		this.rotateSpeed = 1.0; // Set to false to disable panning
+
+		this.enablePan = true;
+		this.panSpeed = 1.0;
+		this.screenSpacePanning = false; // if true, pan in screen-space
+
+		this.keyPanSpeed = 7.0; // pixels moved per arrow key push
+		// Set to true to automatically rotate around the target
+		// If auto-rotate is enabled, you must call controls.update() in your animation loop
+		// auto-rotate is not supported for trackball behavior
+
+		this.autoRotate = false;
+		this.autoRotateSpeed = 2.0; // 30 seconds per round when fps is 60
+		// Set to false to disable use of the keys
+
+		this.enableKeys = true; // The four arrow keys
+
+		this.keys = {
+			LEFT: 37,
+			UP: 38,
+			RIGHT: 39,
+			BOTTOM: 40
+		}; // Mouse buttons
+
+		this.mouseButtons = {
+			LEFT: THREE.MOUSE.ROTATE,
+			MIDDLE: THREE.MOUSE.DOLLY,
+			RIGHT: THREE.MOUSE.PAN
+		}; // Touch fingers
+
+		this.touches = {
+			ONE: THREE.TOUCH.ROTATE,
+			TWO: THREE.TOUCH.DOLLY_PAN
+		}; // for reset
+
+		this.target0 = this.target.clone();
+		this.position0 = this.object.position.clone();
+		this.quaternion0 = this.object.quaternion.clone();
+		this.zoom0 = this.object.zoom; //
+		// public methods
+		//
+
+		this.getPolarAngle = function () {
+
+			return spherical.phi;
+
+		};
+
+		this.getAzimuthalAngle = function () {
+
+			return spherical.theta;
+
+		};
+
+		this.saveState = function () {
+
+			scope.target0.copy( scope.target );
+			scope.position0.copy( scope.object.position );
+			scope.quaternion0.copy( scope.object.quaternion );
+			scope.zoom0 = scope.object.zoom;
+
+		};
+
+		this.reset = function () {
+
+			scope.target.copy( scope.target0 );
+			scope.object.position.copy( scope.position0 );
+			scope.object.quaternion.copy( scope.quaternion0 );
+			scope.object.zoom = scope.zoom0;
+			scope.object.updateProjectionMatrix();
+			scope.dispatchEvent( changeEvent );
+			scope.update();
+			state = STATE.NONE;
+
+		}; // this method is exposed, but perhaps it would be better if we can make it private...
+
+
+		this.update = function () {
+
+			var offset = new THREE.Vector3(); // so camera.up is the orbit axis
+
+			var quat = new THREE.Quaternion().setFromUnitVectors( object.up, new THREE.Vector3( 0, 1, 0 ) );
+			var quatInverse = quat.clone().invert();
+			var lastPosition = new THREE.Vector3();
+			var lastQuaternion = new THREE.Quaternion();
+			var q = new THREE.Quaternion();
+			var vec = new THREE.Vector3();
+			return function update() {
+
+				var position = scope.object.position;
+				offset.copy( position ).sub( scope.target );
+
+				if ( scope.trackball ) {
+
+					// rotate around screen-space y-axis
+					if ( sphericalDelta.theta ) {
+
+						vec.set( 0, 1, 0 ).applyQuaternion( scope.object.quaternion );
+						var factor = scope.enableDamping ? scope.dampingFactor : 1;
+						q.setFromAxisAngle( vec, sphericalDelta.theta * factor );
+						scope.object.quaternion.premultiply( q );
+						offset.applyQuaternion( q );
+
+					} // rotate around screen-space x-axis
+
+
+					if ( sphericalDelta.phi ) {
+
+						vec.set( 1, 0, 0 ).applyQuaternion( scope.object.quaternion );
+						var factor = scope.enableDamping ? scope.dampingFactor : 1;
+						q.setFromAxisAngle( vec, sphericalDelta.phi * factor );
+						scope.object.quaternion.premultiply( q );
+						offset.applyQuaternion( q );
+
+					}
+
+					offset.multiplyScalar( scale );
+					offset.clampLength( scope.minDistance, scope.maxDistance );
+
+				} else {
+
+					// rotate offset to "y-axis-is-up" space
+					offset.applyQuaternion( quat );
+
+					if ( scope.autoRotate && state === STATE.NONE ) {
+
+						rotateLeft( getAutoRotationAngle() );
+
+					}
+
+					spherical.setFromVector3( offset );
+
+					if ( scope.enableDamping ) {
+
+						spherical.theta += sphericalDelta.theta * scope.dampingFactor;
+						spherical.phi += sphericalDelta.phi * scope.dampingFactor;
+
+					} else {
+
+						spherical.theta += sphericalDelta.theta;
+						spherical.phi += sphericalDelta.phi;
+
+					} // restrict theta to be between desired limits
+
+
+					spherical.theta = Math.max( scope.minAzimuthAngle, Math.min( scope.maxAzimuthAngle, spherical.theta ) ); // restrict phi to be between desired limits
+
+					spherical.phi = Math.max( scope.minPolarAngle, Math.min( scope.maxPolarAngle, spherical.phi ) );
+					spherical.makeSafe();
+					spherical.radius *= scale; // restrict radius to be between desired limits
+
+					spherical.radius = Math.max( scope.minDistance, Math.min( scope.maxDistance, spherical.radius ) );
+					offset.setFromSpherical( spherical ); // rotate offset back to "camera-up-vector-is-up" space
+
+					offset.applyQuaternion( quatInverse );
+
+				} // move target to panned location
+
+
+				if ( scope.enableDamping === true ) {
+
+					scope.target.addScaledVector( panOffset, scope.dampingFactor );
+
+				} else {
+
+					scope.target.add( panOffset );
+
+				}
+
+				position.copy( scope.target ).add( offset );
+
+				if ( scope.trackball === false ) {
+
+					scope.object.lookAt( scope.target );
+
+				}
+
+				if ( scope.enableDamping === true ) {
+
+					sphericalDelta.theta *= 1 - scope.dampingFactor;
+					sphericalDelta.phi *= 1 - scope.dampingFactor;
+					panOffset.multiplyScalar( 1 - scope.dampingFactor );
+
+				} else {
+
+					sphericalDelta.set( 0, 0, 0 );
+					panOffset.set( 0, 0, 0 );
+
+				}
+
+				scale = 1; // update condition is:
+				// min(camera displacement, camera rotation in radians)^2 > EPS
+				// using small-angle approximation cos(x/2) = 1 - x^2 / 8
+
+				if ( zoomChanged || lastPosition.distanceToSquared( scope.object.position ) > EPS || 8 * ( 1 - lastQuaternion.dot( scope.object.quaternion ) ) > EPS ) {
+
+					scope.dispatchEvent( changeEvent );
+					lastPosition.copy( scope.object.position );
+					lastQuaternion.copy( scope.object.quaternion );
+					zoomChanged = false;
+					return true;
+
+				}
+
+				return false;
+
+			};
+
+		}();
+
+		this.dispose = function () {
+
+			scope.domElement.removeEventListener( 'contextmenu', onContextMenu, false );
+			scope.domElement.removeEventListener( 'mousedown', onMouseDown, false );
+			scope.domElement.removeEventListener( 'wheel', onMouseWheel, false );
+			scope.domElement.removeEventListener( 'touchstart', onTouchStart, false );
+			scope.domElement.removeEventListener( 'touchend', onTouchEnd, false );
+			scope.domElement.removeEventListener( 'touchmove', onTouchMove, false );
+			document.removeEventListener( 'mousemove', onMouseMove, false );
+			document.removeEventListener( 'mouseup', onMouseUp, false );
+			scope.domElement.removeEventListener( 'keydown', onKeyDown, false ); //scope.dispatchEvent( { type: 'dispose' } ); // should this be added here?
+
+		}; //
+		// internals
+		//
+
+
+		var scope = this;
+		var changeEvent = {
+			type: 'change'
+		};
+		var startEvent = {
+			type: 'start'
+		};
+		var endEvent = {
+			type: 'end'
+		};
+		var STATE = {
+			NONE: - 1,
+			ROTATE: 0,
+			DOLLY: 1,
+			PAN: 2,
+			TOUCH_ROTATE: 3,
+			TOUCH_PAN: 4,
+			TOUCH_DOLLY_PAN: 5,
+			TOUCH_DOLLY_ROTATE: 6
+		};
+		var state = STATE.NONE;
+		var EPS = 0.000001; // current position in spherical coordinates
+
+		var spherical = new THREE.Spherical();
+		var sphericalDelta = new THREE.Spherical();
+		var scale = 1;
+		var panOffset = new THREE.Vector3();
+		var zoomChanged = false;
+		var rotateStart = new THREE.Vector2();
+		var rotateEnd = new THREE.Vector2();
+		var rotateDelta = new THREE.Vector2();
+		var panStart = new THREE.Vector2();
+		var panEnd = new THREE.Vector2();
+		var panDelta = new THREE.Vector2();
+		var dollyStart = new THREE.Vector2();
+		var dollyEnd = new THREE.Vector2();
+		var dollyDelta = new THREE.Vector2();
+
+		function getAutoRotationAngle() {
+
+			return 2 * Math.PI / 60 / 60 * scope.autoRotateSpeed;
+
+		}
+
+		function getZoomScale() {
+
+			return Math.pow( 0.95, scope.zoomSpeed );
+
+		}
+
+		function rotateLeft( angle ) {
+
+			sphericalDelta.theta -= angle;
+
+		}
+
+		function rotateUp( angle ) {
+
+			sphericalDelta.phi -= angle;
+
+		}
+
+		var panLeft = function () {
+
+			var v = new THREE.Vector3();
+			return function panLeft( distance, objectMatrix ) {
+
+				v.setFromMatrixColumn( objectMatrix, 0 ); // get X column of objectMatrix
+
+				v.multiplyScalar( - distance );
+				panOffset.add( v );
+
+			};
+
+		}();
+
+		var panUp = function () {
+
+			var v = new THREE.Vector3();
+			return function panUp( distance, objectMatrix ) {
+
+				if ( scope.screenSpacePanning === true ) {
+
+					v.setFromMatrixColumn( objectMatrix, 1 );
+
+				} else {
+
+					v.setFromMatrixColumn( objectMatrix, 0 );
+					v.crossVectors( scope.object.up, v );
+
+				}
+
+				v.multiplyScalar( distance );
+				panOffset.add( v );
+
+			};
+
+		}(); // deltaX and deltaY are in pixels; right and down are positive
+
+
+		var pan = function () {
+
+			var offset = new THREE.Vector3();
+			return function pan( deltaX, deltaY ) {
+
+				var element = scope.domElement;
+
+				if ( scope.object.isPerspectiveCamera ) {
+
+					// perspective
+					var position = scope.object.position;
+					offset.copy( position ).sub( scope.target );
+					var targetDistance = offset.length(); // half of the fov is center to top of screen
+
+					targetDistance *= Math.tan( scope.object.fov / 2 * Math.PI / 180.0 ); // we use only clientHeight here so aspect ratio does not distort speed
+
+					panLeft( 2 * deltaX * targetDistance / element.clientHeight, scope.object.matrix );
+					panUp( 2 * deltaY * targetDistance / element.clientHeight, scope.object.matrix );
+
+				} else if ( scope.object.isOrthographicCamera ) {
+
+					// orthographic
+					panLeft( deltaX * ( scope.object.right - scope.object.left ) / scope.object.zoom / element.clientWidth, scope.object.matrix );
+					panUp( deltaY * ( scope.object.top - scope.object.bottom ) / scope.object.zoom / element.clientHeight, scope.object.matrix );
+
+				} else {
+
+					// camera neither orthographic nor perspective
+					console.warn( 'WARNING: CameraControls.js encountered an unknown camera type - pan disabled.' );
+					scope.enablePan = false;
+
+				}
+
+			};
+
+		}();
+
+		function dollyIn( dollyScale ) {
+
+			if ( scope.object.isPerspectiveCamera ) {
+
+				scale /= dollyScale;
+
+			} else if ( scope.object.isOrthographicCamera ) {
+
+				scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom * dollyScale ) );
+				scope.object.updateProjectionMatrix();
+				zoomChanged = true;
+
+			} else {
+
+				console.warn( 'WARNING: CameraControls.js encountered an unknown camera type - dolly/zoom disabled.' );
+				scope.enableZoom = false;
+
+			}
+
+		}
+
+		function dollyOut( dollyScale ) {
+
+			if ( scope.object.isPerspectiveCamera ) {
+
+				scale *= dollyScale;
+
+			} else if ( scope.object.isOrthographicCamera ) {
+
+				scope.object.zoom = Math.max( scope.minZoom, Math.min( scope.maxZoom, scope.object.zoom / dollyScale ) );
+				scope.object.updateProjectionMatrix();
+				zoomChanged = true;
+
+			} else {
+
+				console.warn( 'WARNING: CameraControls.js encountered an unknown camera type - dolly/zoom disabled.' );
+				scope.enableZoom = false;
+
+			}
+
+		} //
+		// event callbacks - update the object state
+		//
+
+
+		function handleMouseDownRotate( event ) {
+
+			rotateStart.set( event.clientX, event.clientY );
+
+		}
+
+		function handleMouseDownDolly( event ) {
+
+			dollyStart.set( event.clientX, event.clientY );
+
+		}
+
+		function handleMouseDownPan( event ) {
+
+			panStart.set( event.clientX, event.clientY );
+
+		}
+
+		function handleMouseMoveRotate( event ) {
+
+			rotateEnd.set( event.clientX, event.clientY );
+			rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
+			var element = scope.domElement;
+			rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
+
+			rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
+			rotateStart.copy( rotateEnd );
+			scope.update();
+
+		}
+
+		function handleMouseMoveDolly( event ) {
+
+			dollyEnd.set( event.clientX, event.clientY );
+			dollyDelta.subVectors( dollyEnd, dollyStart );
+
+			if ( dollyDelta.y > 0 ) {
+
+				dollyIn( getZoomScale() );
+
+			} else if ( dollyDelta.y < 0 ) {
+
+				dollyOut( getZoomScale() );
+
+			}
+
+			dollyStart.copy( dollyEnd );
+			scope.update();
+
+		}
+
+		function handleMouseMovePan( event ) {
+
+			panEnd.set( event.clientX, event.clientY );
+			panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
+			pan( panDelta.x, panDelta.y );
+			panStart.copy( panEnd );
+			scope.update();
+
+		}
+
+		function handleMouseUp( ) { // no-op
+		}
+
+		function handleMouseWheel( event ) {
+
+			if ( event.deltaY < 0 ) {
+
+				dollyOut( getZoomScale() );
+
+			} else if ( event.deltaY > 0 ) {
+
+				dollyIn( getZoomScale() );
+
+			}
+
+			scope.update();
+
+		}
+
+		function handleKeyDown( event ) {
+
+			var needsUpdate = false;
+
+			switch ( event.keyCode ) {
+
+				case scope.keys.UP:
+					pan( 0, scope.keyPanSpeed );
+					needsUpdate = true;
+					break;
+
+				case scope.keys.BOTTOM:
+					pan( 0, - scope.keyPanSpeed );
+					needsUpdate = true;
+					break;
+
+				case scope.keys.LEFT:
+					pan( scope.keyPanSpeed, 0 );
+					needsUpdate = true;
+					break;
+
+				case scope.keys.RIGHT:
+					pan( - scope.keyPanSpeed, 0 );
+					needsUpdate = true;
+					break;
+
+			}
+
+			if ( needsUpdate ) {
+
+				// prevent the browser from scrolling on cursor keys
+				event.preventDefault();
+				scope.update();
+
+			}
+
+		}
+
+		function handleTouchStartRotate( event ) {
+
+			if ( event.touches.length == 1 ) {
+
+				rotateStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+
+			} else {
+
+				var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
+				var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
+				rotateStart.set( x, y );
+
+			}
+
+		}
+
+		function handleTouchStartPan( event ) {
+
+			if ( event.touches.length == 1 ) {
+
+				panStart.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+
+			} else {
+
+				var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
+				var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
+				panStart.set( x, y );
+
+			}
+
+		}
+
+		function handleTouchStartDolly( event ) {
+
+			var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+			var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+			var distance = Math.sqrt( dx * dx + dy * dy );
+			dollyStart.set( 0, distance );
+
+		}
+
+		function handleTouchStartDollyPan( event ) {
+
+			if ( scope.enableZoom ) handleTouchStartDolly( event );
+			if ( scope.enablePan ) handleTouchStartPan( event );
+
+		}
+
+		function handleTouchStartDollyRotate( event ) {
+
+			if ( scope.enableZoom ) handleTouchStartDolly( event );
+			if ( scope.enableRotate ) handleTouchStartRotate( event );
+
+		}
+
+		function handleTouchMoveRotate( event ) {
+
+			if ( event.touches.length == 1 ) {
+
+				rotateEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+
+			} else {
+
+				var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
+				var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
+				rotateEnd.set( x, y );
+
+			}
+
+			rotateDelta.subVectors( rotateEnd, rotateStart ).multiplyScalar( scope.rotateSpeed );
+			var element = scope.domElement;
+			rotateLeft( 2 * Math.PI * rotateDelta.x / element.clientHeight ); // yes, height
+
+			rotateUp( 2 * Math.PI * rotateDelta.y / element.clientHeight );
+			rotateStart.copy( rotateEnd );
+
+		}
+
+		function handleTouchMovePan( event ) {
+
+			if ( event.touches.length == 1 ) {
+
+				panEnd.set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY );
+
+			} else {
+
+				var x = 0.5 * ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX );
+				var y = 0.5 * ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY );
+				panEnd.set( x, y );
+
+			}
+
+			panDelta.subVectors( panEnd, panStart ).multiplyScalar( scope.panSpeed );
+			pan( panDelta.x, panDelta.y );
+			panStart.copy( panEnd );
+
+		}
+
+		function handleTouchMoveDolly( event ) {
+
+			var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+			var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+			var distance = Math.sqrt( dx * dx + dy * dy );
+			dollyEnd.set( 0, distance );
+			dollyDelta.set( 0, Math.pow( dollyEnd.y / dollyStart.y, scope.zoomSpeed ) );
+			dollyIn( dollyDelta.y );
+			dollyStart.copy( dollyEnd );
+
+		}
+
+		function handleTouchMoveDollyPan( event ) {
+
+			if ( scope.enableZoom ) handleTouchMoveDolly( event );
+			if ( scope.enablePan ) handleTouchMovePan( event );
+
+		}
+
+		function handleTouchMoveDollyRotate( event ) {
+
+			if ( scope.enableZoom ) handleTouchMoveDolly( event );
+			if ( scope.enableRotate ) handleTouchMoveRotate( event );
+
+		}
+
+		function handleTouchEnd( ) { // no-op
+		} //
+		// event handlers - FSM: listen for events and reset state
+		//
+
+
+		function onMouseDown( event ) {
+
+			if ( scope.enabled === false ) return; // Prevent the browser from scrolling.
+
+			event.preventDefault(); // Manually set the focus since calling preventDefault above
+			// prevents the browser from setting it automatically.
+
+			scope.domElement.focus ? scope.domElement.focus() : window.focus();
+			var mouseAction;
+
+			switch ( event.button ) {
+
+				case 0:
+					mouseAction = scope.mouseButtons.LEFT;
+					break;
+
+				case 1:
+					mouseAction = scope.mouseButtons.MIDDLE;
+					break;
+
+				case 2:
+					mouseAction = scope.mouseButtons.RIGHT;
+					break;
+
+				default:
+					mouseAction = - 1;
+
+			}
+
+			switch ( mouseAction ) {
+
+				case THREE.MOUSE.DOLLY:
+					if ( scope.enableZoom === false ) return;
+					handleMouseDownDolly( event );
+					state = STATE.DOLLY;
+					break;
+
+				case THREE.MOUSE.ROTATE:
+					if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
+
+						if ( scope.enablePan === false ) return;
+						handleMouseDownPan( event );
+						state = STATE.PAN;
+
+					} else {
+
+						if ( scope.enableRotate === false ) return;
+						handleMouseDownRotate( event );
+						state = STATE.ROTATE;
+
+					}
+
+					break;
+
+				case THREE.MOUSE.PAN:
+					if ( event.ctrlKey || event.metaKey || event.shiftKey ) {
+
+						if ( scope.enableRotate === false ) return;
+						handleMouseDownRotate( event );
+						state = STATE.ROTATE;
+
+					} else {
+
+						if ( scope.enablePan === false ) return;
+						handleMouseDownPan( event );
+						state = STATE.PAN;
+
+					}
+
+					break;
+
+				default:
+					state = STATE.NONE;
+
+			}
+
+			if ( state !== STATE.NONE ) {
+
+				document.addEventListener( 'mousemove', onMouseMove, false );
+				document.addEventListener( 'mouseup', onMouseUp, false );
+				scope.dispatchEvent( startEvent );
+
+			}
+
+		}
+
+		function onMouseMove( event ) {
+
+			if ( scope.enabled === false ) return;
+			event.preventDefault();
+
+			switch ( state ) {
+
+				case STATE.ROTATE:
+					if ( scope.enableRotate === false ) return;
+					handleMouseMoveRotate( event );
+					break;
+
+				case STATE.DOLLY:
+					if ( scope.enableZoom === false ) return;
+					handleMouseMoveDolly( event );
+					break;
+
+				case STATE.PAN:
+					if ( scope.enablePan === false ) return;
+					handleMouseMovePan( event );
+					break;
+
+			}
+
+		}
+
+		function onMouseUp( event ) {
+
+			if ( scope.enabled === false ) return;
+			handleMouseUp( event );
+			document.removeEventListener( 'mousemove', onMouseMove, false );
+			document.removeEventListener( 'mouseup', onMouseUp, false );
+			scope.dispatchEvent( endEvent );
+			state = STATE.NONE;
+
+		}
+
+		function onMouseWheel( event ) {
+
+			if ( scope.enabled === false || scope.enableZoom === false || state !== STATE.NONE && state !== STATE.ROTATE ) return;
+			event.preventDefault();
+			event.stopPropagation();
+			scope.dispatchEvent( startEvent );
+			handleMouseWheel( event );
+			scope.dispatchEvent( endEvent );
+
+		}
+
+		function onKeyDown( event ) {
+
+			if ( scope.enabled === false || scope.enableKeys === false || scope.enablePan === false ) return;
+			handleKeyDown( event );
+
+		}
+
+		function onTouchStart( event ) {
+
+			if ( scope.enabled === false ) return;
+			event.preventDefault();
+
+			switch ( event.touches.length ) {
+
+				case 1:
+					switch ( scope.touches.ONE ) {
+
+						case THREE.TOUCH.ROTATE:
+							if ( scope.enableRotate === false ) return;
+							handleTouchStartRotate( event );
+							state = STATE.TOUCH_ROTATE;
+							break;
+
+						case THREE.TOUCH.PAN:
+							if ( scope.enablePan === false ) return;
+							handleTouchStartPan( event );
+							state = STATE.TOUCH_PAN;
+							break;
+
+						default:
+							state = STATE.NONE;
+
+					}
+
+					break;
+
+				case 2:
+					switch ( scope.touches.TWO ) {
+
+						case THREE.TOUCH.DOLLY_PAN:
+							if ( scope.enableZoom === false && scope.enablePan === false ) return;
+							handleTouchStartDollyPan( event );
+							state = STATE.TOUCH_DOLLY_PAN;
+							break;
+
+						case THREE.TOUCH.DOLLY_ROTATE:
+							if ( scope.enableZoom === false && scope.enableRotate === false ) return;
+							handleTouchStartDollyRotate( event );
+							state = STATE.TOUCH_DOLLY_ROTATE;
+							break;
+
+						default:
+							state = STATE.NONE;
+
+					}
+
+					break;
+
+				default:
+					state = STATE.NONE;
+
+			}
+
+			if ( state !== STATE.NONE ) {
+
+				scope.dispatchEvent( startEvent );
+
+			}
+
+		}
+
+		function onTouchMove( event ) {
+
+			if ( scope.enabled === false ) return;
+			event.preventDefault();
+			event.stopPropagation();
+
+			switch ( state ) {
+
+				case STATE.TOUCH_ROTATE:
+					if ( scope.enableRotate === false ) return;
+					handleTouchMoveRotate( event );
+					scope.update();
+					break;
+
+				case STATE.TOUCH_PAN:
+					if ( scope.enablePan === false ) return;
+					handleTouchMovePan( event );
+					scope.update();
+					break;
+
+				case STATE.TOUCH_DOLLY_PAN:
+					if ( scope.enableZoom === false && scope.enablePan === false ) return;
+					handleTouchMoveDollyPan( event );
+					scope.update();
+					break;
+
+				case STATE.TOUCH_DOLLY_ROTATE:
+					if ( scope.enableZoom === false && scope.enableRotate === false ) return;
+					handleTouchMoveDollyRotate( event );
+					scope.update();
+					break;
+
+				default:
+					state = STATE.NONE;
+
+			}
+
+		}
+
+		function onTouchEnd( event ) {
+
+			if ( scope.enabled === false ) return;
+			handleTouchEnd( event );
+			scope.dispatchEvent( endEvent );
+			state = STATE.NONE;
+
+		}
+
+		function onContextMenu( event ) {
+
+			if ( scope.enabled === false ) return;
+			event.preventDefault();
+
+		} //
+
+
+		scope.domElement.addEventListener( 'contextmenu', onContextMenu, false );
+		scope.domElement.addEventListener( 'mousedown', onMouseDown, false );
+		scope.domElement.addEventListener( 'wheel', onMouseWheel, false );
+		scope.domElement.addEventListener( 'touchstart', onTouchStart, false );
+		scope.domElement.addEventListener( 'touchend', onTouchEnd, false );
+		scope.domElement.addEventListener( 'touchmove', onTouchMove, false );
+		scope.domElement.addEventListener( 'keydown', onKeyDown, false ); // make sure element can receive keys.
+
+		if ( scope.domElement.tabIndex === - 1 ) {
+
+			scope.domElement.tabIndex = 0;
+
+		} // force an update at start
+
+
+		this.object.lookAt( scope.target );
+		this.update();
+		this.saveState();
+
+	};
+
+	CameraControls.prototype = Object.create( THREE.EventDispatcher.prototype );
+	CameraControls.prototype.constructor = CameraControls; // OrbitControls maintains the "up" direction, camera.up (+Y by default).
+	//
+	//		Orbit - left mouse / touch: one-finger move
+	//		Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
+	//		Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
+
+	var OrbitControls = function ( object, domElement ) {
+
+		CameraControls.call( this, object, domElement );
+		this.mouseButtons.LEFT = THREE.MOUSE.ROTATE;
+		this.mouseButtons.RIGHT = THREE.MOUSE.PAN;
+		this.touches.ONE = THREE.TOUCH.ROTATE;
+		this.touches.TWO = THREE.TOUCH.DOLLY_PAN;
+
+	};
+
+	OrbitControls.prototype = Object.create( THREE.EventDispatcher.prototype );
+	OrbitControls.prototype.constructor = OrbitControls; // MapControls maintains the "up" direction, camera.up (+Y by default)
+	//
+	//		Orbit - right mouse, or left mouse + ctrl/meta/shiftKey / touch: two-finger rotate
+	//		Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
+	//		Pan - left mouse, or left right + ctrl/meta/shiftKey, or arrow keys / touch: one-finger move
+
+	var MapControls = function ( object, domElement ) {
+
+		CameraControls.call( this, object, domElement );
+		this.mouseButtons.LEFT = THREE.MOUSE.PAN;
+		this.mouseButtons.RIGHT = THREE.MOUSE.ROTATE;
+		this.touches.ONE = THREE.TOUCH.PAN;
+		this.touches.TWO = THREE.TOUCH.DOLLY_ROTATE;
+
+	};
+
+	MapControls.prototype = Object.create( THREE.EventDispatcher.prototype );
+	MapControls.prototype.constructor = MapControls; // TrackballControls allows the camera to rotate over the polls and does not maintain camera.up
+	//
+	//		Orbit - left mouse / touch: one-finger move
+	//		Zoom - middle mouse, or mousewheel / touch: two-finger spread or squish
+	//		Pan - right mouse, or left mouse + ctrl/meta/shiftKey, or arrow keys / touch: two-finger move
+
+	var TrackballControls = function ( object, domElement ) {
+
+		CameraControls.call( this, object, domElement );
+		this.trackball = true;
+		this.screenSpacePanning = true;
+		this.autoRotate = false;
+		this.mouseButtons.LEFT = THREE.MOUSE.ROTATE;
+		this.mouseButtons.RIGHT = THREE.MOUSE.PAN;
+		this.touches.ONE = THREE.TOUCH.ROTATE;
+		this.touches.TWO = THREE.TOUCH.DOLLY_PAN;
+
+	};
+
+	TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype );
+	TrackballControls.prototype.constructor = TrackballControls;
+
+	THREE.CameraControls = CameraControls;
+	THREE.MapControls = MapControls;
+	THREE.OrbitControls = OrbitControls;
+	THREE.TrackballControls = TrackballControls;
+
+} )();

+ 382 - 0
examples/js/csm/CSM.js

@@ -0,0 +1,382 @@
+( function () {
+
+	const _cameraToLightMatrix = new THREE.Matrix4();
+
+	const _lightSpaceFrustum = new THREE.Frustum();
+
+	const _center = new THREE.Vector3();
+
+	const _bbox = new THREE.Box3();
+
+	const _uniformArray = [];
+	const _logArray = [];
+	class CSM {
+
+		constructor( data ) {
+
+			data = data || {};
+			this.camera = data.camera;
+			this.parent = data.parent;
+			this.cascades = data.cascades || 3;
+			this.maxFar = data.maxFar || 100000;
+			this.mode = data.mode || 'practical';
+			this.shadowMapSize = data.shadowMapSize || 2048;
+			this.shadowBias = data.shadowBias || 0.000001;
+			this.lightDirection = data.lightDirection || new THREE.Vector3( 1, - 1, 1 ).normalize();
+			this.lightIntensity = data.lightIntensity || 1;
+			this.lightNear = data.lightNear || 1;
+			this.lightFar = data.lightFar || 2000;
+			this.lightMargin = data.lightMargin || 200;
+			this.customSplitsCallback = data.customSplitsCallback;
+			this.fade = false;
+			this.mainFrustum = new THREE.Frustum();
+			this.frustums = [];
+			this.breaks = [];
+			this.lights = [];
+			this.shaders = new Map();
+			this.createLights();
+			this.updateFrustums();
+			this.injectInclude();
+
+		}
+
+		createLights() {
+
+			for ( let i = 0; i < this.cascades; i ++ ) {
+
+				const light = new THREE.DirectionalLight( 0xffffff, this.lightIntensity );
+				light.castShadow = true;
+				light.shadow.mapSize.width = this.shadowMapSize;
+				light.shadow.mapSize.height = this.shadowMapSize;
+				light.shadow.camera.near = this.lightNear;
+				light.shadow.camera.far = this.lightFar;
+				light.shadow.bias = this.shadowBias;
+				this.parent.add( light );
+				this.parent.add( light.target );
+				this.lights.push( light );
+
+			}
+
+		}
+
+		initCascades() {
+
+			const camera = this.camera;
+			camera.updateProjectionMatrix();
+			this.mainFrustum.setFromProjectionMatrix( camera.projectionMatrix, this.maxFar );
+			this.mainFrustum.split( this.breaks, this.frustums );
+
+		}
+
+		updateShadowBounds() {
+
+			const frustums = this.frustums;
+
+			for ( let i = 0; i < frustums.length; i ++ ) {
+
+				const light = this.lights[ i ];
+				const shadowCam = light.shadow.camera;
+				const frustum = this.frustums[ i ]; // Get the two points that represent that furthest points on the frustum assuming
+				// that's either the diagonal across the far plane or the diagonal across the whole
+				// frustum itself.
+
+				const nearVerts = frustum.vertices.near;
+				const farVerts = frustum.vertices.far;
+				const point1 = farVerts[ 0 ];
+				let point2;
+
+				if ( point1.distanceTo( farVerts[ 2 ] ) > point1.distanceTo( nearVerts[ 2 ] ) ) {
+
+					point2 = farVerts[ 2 ];
+
+				} else {
+
+					point2 = nearVerts[ 2 ];
+
+				}
+
+				let squaredBBWidth = point1.distanceTo( point2 );
+
+				if ( this.fade ) {
+
+					// expand the shadow extents by the fade margin if fade is enabled.
+					const camera = this.camera;
+					const far = Math.max( camera.far, this.maxFar );
+					const linearDepth = frustum.vertices.far[ 0 ].z / ( far - camera.near );
+					const margin = 0.25 * Math.pow( linearDepth, 2.0 ) * ( far - camera.near );
+					squaredBBWidth += margin;
+
+				}
+
+				shadowCam.left = - squaredBBWidth / 2;
+				shadowCam.right = squaredBBWidth / 2;
+				shadowCam.top = squaredBBWidth / 2;
+				shadowCam.bottom = - squaredBBWidth / 2;
+				shadowCam.updateProjectionMatrix();
+
+			}
+
+		}
+
+		getBreaks() {
+
+			const camera = this.camera;
+			const far = Math.min( camera.far, this.maxFar );
+			this.breaks.length = 0;
+
+			switch ( this.mode ) {
+
+				case 'uniform':
+					uniformSplit( this.cascades, camera.near, far, this.breaks );
+					break;
+
+				case 'logarithmic':
+					logarithmicSplit( this.cascades, camera.near, far, this.breaks );
+					break;
+
+				case 'practical':
+					practicalSplit( this.cascades, camera.near, far, 0.5, this.breaks );
+					break;
+
+				case 'custom':
+					if ( this.customSplitsCallback === undefined ) console.error( 'CSM: Custom split scheme callback not defined.' );
+					this.customSplitsCallback( this.cascades, camera.near, far, this.breaks );
+					break;
+
+			}
+
+			function uniformSplit( amount, near, far, target ) {
+
+				for ( let i = 1; i < amount; i ++ ) {
+
+					target.push( ( near + ( far - near ) * i / amount ) / far );
+
+				}
+
+				target.push( 1 );
+
+			}
+
+			function logarithmicSplit( amount, near, far, target ) {
+
+				for ( let i = 1; i < amount; i ++ ) {
+
+					target.push( near * ( far / near ) ** ( i / amount ) / far );
+
+				}
+
+				target.push( 1 );
+
+			}
+
+			function practicalSplit( amount, near, far, lambda, target ) {
+
+				_uniformArray.length = 0;
+				_logArray.length = 0;
+				logarithmicSplit( amount, near, far, _logArray );
+				uniformSplit( amount, near, far, _uniformArray );
+
+				for ( let i = 1; i < amount; i ++ ) {
+
+					target.push( THREE.MathUtils.lerp( _uniformArray[ i - 1 ], _logArray[ i - 1 ], lambda ) );
+
+				}
+
+				target.push( 1 );
+
+			}
+
+		}
+
+		update() {
+
+			const camera = this.camera;
+			const frustums = this.frustums;
+
+			for ( let i = 0; i < frustums.length; i ++ ) {
+
+				const light = this.lights[ i ];
+				const shadowCam = light.shadow.camera;
+				const texelWidth = ( shadowCam.right - shadowCam.left ) / this.shadowMapSize;
+				const texelHeight = ( shadowCam.top - shadowCam.bottom ) / this.shadowMapSize;
+				light.shadow.camera.updateMatrixWorld( true );
+
+				_cameraToLightMatrix.multiplyMatrices( light.shadow.camera.matrixWorldInverse, camera.matrixWorld );
+
+				frustums[ i ].toSpace( _cameraToLightMatrix, _lightSpaceFrustum );
+				const nearVerts = _lightSpaceFrustum.vertices.near;
+				const farVerts = _lightSpaceFrustum.vertices.far;
+
+				_bbox.makeEmpty();
+
+				for ( let j = 0; j < 4; j ++ ) {
+
+					_bbox.expandByPoint( nearVerts[ j ] );
+
+					_bbox.expandByPoint( farVerts[ j ] );
+
+				}
+
+				_bbox.getCenter( _center );
+
+				_center.z = _bbox.max.z + this.lightMargin;
+				_center.x = Math.floor( _center.x / texelWidth ) * texelWidth;
+				_center.y = Math.floor( _center.y / texelHeight ) * texelHeight;
+
+				_center.applyMatrix4( light.shadow.camera.matrixWorld );
+
+				light.position.copy( _center );
+				light.target.position.copy( _center );
+				light.target.position.x += this.lightDirection.x;
+				light.target.position.y += this.lightDirection.y;
+				light.target.position.z += this.lightDirection.z;
+
+			}
+
+		}
+
+		injectInclude() {
+
+			THREE.ShaderChunk.lights_fragment_begin = THREE.CSMShader.lights_fragment_begin;
+			THREE.ShaderChunk.lights_pars_begin = THREE.CSMShader.lights_pars_begin;
+
+		}
+
+		setupMaterial( material ) {
+
+			material.defines = material.defines || {};
+			material.defines.USE_CSM = 1;
+			material.defines.CSM_CASCADES = this.cascades;
+
+			if ( this.fade ) {
+
+				material.defines.CSM_FADE = '';
+
+			}
+
+			const breaksVec2 = [];
+			const scope = this;
+			const shaders = this.shaders;
+
+			material.onBeforeCompile = function ( shader ) {
+
+				const far = Math.min( scope.camera.far, scope.maxFar );
+				scope.getExtendedBreaks( breaksVec2 );
+				shader.uniforms.CSM_cascades = {
+					value: breaksVec2
+				};
+				shader.uniforms.cameraNear = {
+					value: scope.camera.near
+				};
+				shader.uniforms.shadowFar = {
+					value: far
+				};
+				shaders.set( material, shader );
+
+			};
+
+			shaders.set( material, null );
+
+		}
+
+		updateUniforms() {
+
+			const far = Math.min( this.camera.far, this.maxFar );
+			const shaders = this.shaders;
+			shaders.forEach( function ( shader, material ) {
+
+				if ( shader !== null ) {
+
+					const uniforms = shader.uniforms;
+					this.getExtendedBreaks( uniforms.CSM_cascades.value );
+					uniforms.cameraNear.value = this.camera.near;
+					uniforms.shadowFar.value = far;
+
+				}
+
+				if ( ! this.fade && 'CSM_FADE' in material.defines ) {
+
+					delete material.defines.CSM_FADE;
+					material.needsUpdate = true;
+
+				} else if ( this.fade && ! ( 'CSM_FADE' in material.defines ) ) {
+
+					material.defines.CSM_FADE = '';
+					material.needsUpdate = true;
+
+				}
+
+			}, this );
+
+		}
+
+		getExtendedBreaks( target ) {
+
+			while ( target.length < this.breaks.length ) {
+
+				target.push( new THREE.Vector2() );
+
+			}
+
+			target.length = this.breaks.length;
+
+			for ( let i = 0; i < this.cascades; i ++ ) {
+
+				const amount = this.breaks[ i ];
+				const prev = this.breaks[ i - 1 ] || 0;
+				target[ i ].x = prev;
+				target[ i ].y = amount;
+
+			}
+
+		}
+
+		updateFrustums() {
+
+			this.getBreaks();
+			this.initCascades();
+			this.updateShadowBounds();
+			this.updateUniforms();
+
+		}
+
+		remove() {
+
+			for ( let i = 0; i < this.lights.length; i ++ ) {
+
+				this.parent.remove( this.lights[ i ] );
+
+			}
+
+		}
+
+		dispose() {
+
+			const shaders = this.shaders;
+			shaders.forEach( function ( shader, material ) {
+
+				delete material.onBeforeCompile;
+				delete material.defines.USE_CSM;
+				delete material.defines.CSM_CASCADES;
+				delete material.defines.CSM_FADE;
+
+				if ( shader !== null ) {
+
+					delete shader.uniforms.CSM_cascades;
+					delete shader.uniforms.cameraNear;
+					delete shader.uniforms.shadowFar;
+
+				}
+
+				material.needsUpdate = true;
+
+			} );
+			shaders.clear();
+
+		}
+
+	}
+
+	THREE.CSM = CSM;
+
+} )();

+ 145 - 0
examples/js/csm/CSMHelper.js

@@ -0,0 +1,145 @@
+( function () {
+
+	class CSMHelper extends THREE.Group {
+
+		constructor( csm ) {
+
+			super();
+			this.csm = csm;
+			this.displayFrustum = true;
+			this.displayPlanes = true;
+			this.displayShadowBounds = true;
+			const indices = new Uint16Array( [ 0, 1, 1, 2, 2, 3, 3, 0, 4, 5, 5, 6, 6, 7, 7, 4, 0, 4, 1, 5, 2, 6, 3, 7 ] );
+			const positions = new Float32Array( 24 );
+			const frustumGeometry = new THREE.BufferGeometry();
+			frustumGeometry.setIndex( new THREE.BufferAttribute( indices, 1 ) );
+			frustumGeometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3, false ) );
+			const frustumLines = new THREE.LineSegments( frustumGeometry, new THREE.LineBasicMaterial() );
+			this.add( frustumLines );
+			this.frustumLines = frustumLines;
+			this.cascadeLines = [];
+			this.cascadePlanes = [];
+			this.shadowLines = [];
+
+		}
+
+		updateVisibility() {
+
+			const displayFrustum = this.displayFrustum;
+			const displayPlanes = this.displayPlanes;
+			const displayShadowBounds = this.displayShadowBounds;
+			const frustumLines = this.frustumLines;
+			const cascadeLines = this.cascadeLines;
+			const cascadePlanes = this.cascadePlanes;
+			const shadowLines = this.shadowLines;
+
+			for ( let i = 0, l = cascadeLines.length; i < l; i ++ ) {
+
+				const cascadeLine = cascadeLines[ i ];
+				const cascadePlane = cascadePlanes[ i ];
+				const shadowLineGroup = shadowLines[ i ];
+				cascadeLine.visible = displayFrustum;
+				cascadePlane.visible = displayFrustum && displayPlanes;
+				shadowLineGroup.visible = displayShadowBounds;
+
+			}
+
+			frustumLines.visible = displayFrustum;
+
+		}
+
+		update() {
+
+			const csm = this.csm;
+			const camera = csm.camera;
+			const cascades = csm.cascades;
+			const mainFrustum = csm.mainFrustum;
+			const frustums = csm.frustums;
+			const lights = csm.lights;
+			const frustumLines = this.frustumLines;
+			const frustumLinePositions = frustumLines.geometry.getAttribute( 'position' );
+			const cascadeLines = this.cascadeLines;
+			const cascadePlanes = this.cascadePlanes;
+			const shadowLines = this.shadowLines;
+			this.position.copy( camera.position );
+			this.quaternion.copy( camera.quaternion );
+			this.scale.copy( camera.scale );
+			this.updateMatrixWorld( true );
+
+			while ( cascadeLines.length > cascades ) {
+
+				this.remove( cascadeLines.pop() );
+				this.remove( cascadePlanes.pop() );
+				this.remove( shadowLines.pop() );
+
+			}
+
+			while ( cascadeLines.length < cascades ) {
+
+				const cascadeLine = new THREE.Box3Helper( new THREE.Box3(), 0xffffff );
+				const planeMat = new THREE.MeshBasicMaterial( {
+					transparent: true,
+					opacity: 0.1,
+					depthWrite: false,
+					side: THREE.DoubleSide
+				} );
+				const cascadePlane = new THREE.Mesh( new THREE.PlaneGeometry(), planeMat );
+				const shadowLineGroup = new THREE.Group();
+				const shadowLine = new THREE.Box3Helper( new THREE.Box3(), 0xffff00 );
+				shadowLineGroup.add( shadowLine );
+				this.add( cascadeLine );
+				this.add( cascadePlane );
+				this.add( shadowLineGroup );
+				cascadeLines.push( cascadeLine );
+				cascadePlanes.push( cascadePlane );
+				shadowLines.push( shadowLineGroup );
+
+			}
+
+			for ( let i = 0; i < cascades; i ++ ) {
+
+				const frustum = frustums[ i ];
+				const light = lights[ i ];
+				const shadowCam = light.shadow.camera;
+				const farVerts = frustum.vertices.far;
+				const cascadeLine = cascadeLines[ i ];
+				const cascadePlane = cascadePlanes[ i ];
+				const shadowLineGroup = shadowLines[ i ];
+				const shadowLine = shadowLineGroup.children[ 0 ];
+				cascadeLine.box.min.copy( farVerts[ 2 ] );
+				cascadeLine.box.max.copy( farVerts[ 0 ] );
+				cascadeLine.box.max.z += 1e-4;
+				cascadePlane.position.addVectors( farVerts[ 0 ], farVerts[ 2 ] );
+				cascadePlane.position.multiplyScalar( 0.5 );
+				cascadePlane.scale.subVectors( farVerts[ 0 ], farVerts[ 2 ] );
+				cascadePlane.scale.z = 1e-4;
+				this.remove( shadowLineGroup );
+				shadowLineGroup.position.copy( shadowCam.position );
+				shadowLineGroup.quaternion.copy( shadowCam.quaternion );
+				shadowLineGroup.scale.copy( shadowCam.scale );
+				shadowLineGroup.updateMatrixWorld( true );
+				this.attach( shadowLineGroup );
+				shadowLine.box.min.set( shadowCam.bottom, shadowCam.left, - shadowCam.far );
+				shadowLine.box.max.set( shadowCam.top, shadowCam.right, - shadowCam.near );
+
+			}
+
+			const nearVerts = mainFrustum.vertices.near;
+			const farVerts = mainFrustum.vertices.far;
+			frustumLinePositions.setXYZ( 0, farVerts[ 0 ].x, farVerts[ 0 ].y, farVerts[ 0 ].z );
+			frustumLinePositions.setXYZ( 1, farVerts[ 3 ].x, farVerts[ 3 ].y, farVerts[ 3 ].z );
+			frustumLinePositions.setXYZ( 2, farVerts[ 2 ].x, farVerts[ 2 ].y, farVerts[ 2 ].z );
+			frustumLinePositions.setXYZ( 3, farVerts[ 1 ].x, farVerts[ 1 ].y, farVerts[ 1 ].z );
+			frustumLinePositions.setXYZ( 4, nearVerts[ 0 ].x, nearVerts[ 0 ].y, nearVerts[ 0 ].z );
+			frustumLinePositions.setXYZ( 5, nearVerts[ 3 ].x, nearVerts[ 3 ].y, nearVerts[ 3 ].z );
+			frustumLinePositions.setXYZ( 6, nearVerts[ 2 ].x, nearVerts[ 2 ].y, nearVerts[ 2 ].z );
+			frustumLinePositions.setXYZ( 7, nearVerts[ 1 ].x, nearVerts[ 1 ].y, nearVerts[ 1 ].z );
+			frustumLinePositions.needsUpdate = true;
+
+		}
+
+	}
+
+	THREE.CSMHelper = CSMHelper;
+
+} )();

+ 241 - 0
examples/js/csm/CSMShader.js

@@ -0,0 +1,241 @@
+( function () {
+
+	const CSMShader = {
+		lights_fragment_begin:
+	/* glsl */
+	`
+GeometricContext geometry;
+
+geometry.position = - vViewPosition;
+geometry.normal = normal;
+geometry.viewDir = ( isOrthographic ) ? vec3( 0, 0, 1 ) : normalize( vViewPosition );
+
+#ifdef CLEARCOAT
+
+	geometry.clearcoatNormal = clearcoatNormal;
+
+#endif
+
+IncidentLight directLight;
+
+#if ( NUM_POINT_LIGHTS > 0 ) && defined( RE_Direct )
+
+	PointLight pointLight;
+	#if defined( USE_SHADOWMAP ) && NUM_POINT_LIGHT_SHADOWS > 0
+	PointLightShadow pointLightShadow;
+	#endif
+
+	#pragma unroll_loop_start
+	for ( int i = 0; i < NUM_POINT_LIGHTS; i ++ ) {
+
+		pointLight = pointLights[ i ];
+
+		getPointDirectLightIrradiance( pointLight, geometry, directLight );
+
+		#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_POINT_LIGHT_SHADOWS )
+		pointLightShadow = pointLightShadows[ i ];
+		directLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getPointShadow( pointShadowMap[ i ], pointLightShadow.shadowMapSize, pointLightShadow.shadowBias, pointLightShadow.shadowRadius, vPointShadowCoord[ i ], pointLightShadow.shadowCameraNear, pointLightShadow.shadowCameraFar ) : 1.0;
+		#endif
+
+		RE_Direct( directLight, geometry, material, reflectedLight );
+
+	}
+	#pragma unroll_loop_end
+
+#endif
+
+#if ( NUM_SPOT_LIGHTS > 0 ) && defined( RE_Direct )
+
+	SpotLight spotLight;
+	#if defined( USE_SHADOWMAP ) && NUM_SPOT_LIGHT_SHADOWS > 0
+	SpotLightShadow spotLightShadow;
+	#endif
+
+	#pragma unroll_loop_start
+	for ( int i = 0; i < NUM_SPOT_LIGHTS; i ++ ) {
+
+		spotLight = spotLights[ i ];
+
+		getSpotDirectLightIrradiance( spotLight, geometry, directLight );
+
+		#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_SPOT_LIGHT_SHADOWS )
+		spotLightShadow = spotLightShadows[ i ];
+		directLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( spotShadowMap[ i ], spotLightShadow.shadowMapSize, spotLightShadow.shadowBias, spotLightShadow.shadowRadius, vSpotShadowCoord[ i ] ) : 1.0;
+		#endif
+
+		RE_Direct( directLight, geometry, material, reflectedLight );
+
+	}
+	#pragma unroll_loop_end
+
+#endif
+
+#if ( NUM_DIR_LIGHTS > 0) && defined( RE_Direct ) && defined( USE_CSM ) && defined( CSM_CASCADES )
+
+	DirectionalLight directionalLight;
+	float linearDepth = (vViewPosition.z) / (shadowFar - cameraNear);
+	#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0
+	DirectionalLightShadow directionalLightShadow;
+	#endif
+
+	#if defined( USE_SHADOWMAP ) && defined( CSM_FADE )
+	vec2 cascade;
+	float cascadeCenter;
+	float closestEdge;
+	float margin;
+	float csmx;
+	float csmy;
+
+	#pragma unroll_loop_start
+	for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {
+
+		directionalLight = directionalLights[ i ];
+		getDirectionalDirectLightIrradiance( directionalLight, geometry, directLight );
+
+		// NOTE: Depth gets larger away from the camera.
+		// cascade.x is closer, cascade.y is further
+		cascade = CSM_cascades[ i ];
+		cascadeCenter = ( cascade.x + cascade.y ) / 2.0;
+		closestEdge = linearDepth < cascadeCenter ? cascade.x : cascade.y;
+		margin = 0.25 * pow( closestEdge, 2.0 );
+		csmx = cascade.x - margin / 2.0;
+		csmy = cascade.y + margin / 2.0;
+		if( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS && linearDepth >= csmx && ( linearDepth < csmy || UNROLLED_LOOP_INDEX == CSM_CASCADES - 1 ) ) {
+
+			float dist = min( linearDepth - csmx, csmy - linearDepth );
+			float ratio = clamp( dist / margin, 0.0, 1.0 );
+			if( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS ) {
+
+				vec3 prevColor = directLight.color;
+				directionalLightShadow = directionalLightShadows[ i ];
+				directLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;
+
+				bool shouldFadeLastCascade = UNROLLED_LOOP_INDEX == CSM_CASCADES - 1 && linearDepth > cascadeCenter;
+				directLight.color = mix( prevColor, directLight.color, shouldFadeLastCascade ? ratio : 1.0 );
+
+			}
+
+			ReflectedLight prevLight = reflectedLight;
+			RE_Direct( directLight, geometry, material, reflectedLight );
+
+			bool shouldBlend = UNROLLED_LOOP_INDEX != CSM_CASCADES - 1 || UNROLLED_LOOP_INDEX == CSM_CASCADES - 1 && linearDepth < cascadeCenter;
+			float blendRatio = shouldBlend ? ratio : 1.0;
+
+			reflectedLight.directDiffuse = mix( prevLight.directDiffuse, reflectedLight.directDiffuse, blendRatio );
+			reflectedLight.directSpecular = mix( prevLight.directSpecular, reflectedLight.directSpecular, blendRatio );
+			reflectedLight.indirectDiffuse = mix( prevLight.indirectDiffuse, reflectedLight.indirectDiffuse, blendRatio );
+			reflectedLight.indirectSpecular = mix( prevLight.indirectSpecular, reflectedLight.indirectSpecular, blendRatio );
+
+		}
+
+	}
+	#pragma unroll_loop_end
+	#else
+
+	#pragma unroll_loop_start
+	for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {
+
+		directionalLight = directionalLights[ i ];
+		getDirectionalDirectLightIrradiance( directionalLight, geometry, directLight );
+
+		#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )
+
+		directionalLightShadow = directionalLightShadows[ i ];
+		if(linearDepth >= CSM_cascades[UNROLLED_LOOP_INDEX].x && linearDepth < CSM_cascades[UNROLLED_LOOP_INDEX].y) directLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;
+
+		#endif
+
+		if(linearDepth >= CSM_cascades[UNROLLED_LOOP_INDEX].x && (linearDepth < CSM_cascades[UNROLLED_LOOP_INDEX].y || UNROLLED_LOOP_INDEX == CSM_CASCADES - 1)) RE_Direct( directLight, geometry, material, reflectedLight );
+
+	}
+	#pragma unroll_loop_end
+
+	#endif
+
+#endif
+
+
+#if ( NUM_DIR_LIGHTS > 0 ) && defined( RE_Direct ) && !defined( USE_CSM ) && !defined( CSM_CASCADES )
+
+	DirectionalLight directionalLight;
+	#if defined( USE_SHADOWMAP ) && NUM_DIR_LIGHT_SHADOWS > 0
+	DirectionalLightShadow directionalLightShadow;
+	#endif
+
+	#pragma unroll_loop_start
+	for ( int i = 0; i < NUM_DIR_LIGHTS; i ++ ) {
+
+		directionalLight = directionalLights[ i ];
+
+		getDirectionalDirectLightIrradiance( directionalLight, geometry, directLight );
+
+		#if defined( USE_SHADOWMAP ) && ( UNROLLED_LOOP_INDEX < NUM_DIR_LIGHT_SHADOWS )
+		directionalLightShadow = directionalLightShadows[ i ];
+		directLight.color *= all( bvec2( directLight.visible, receiveShadow ) ) ? getShadow( directionalShadowMap[ i ], directionalLightShadow.shadowMapSize, directionalLightShadow.shadowBias, directionalLightShadow.shadowRadius, vDirectionalShadowCoord[ i ] ) : 1.0;
+		#endif
+
+		RE_Direct( directLight, geometry, material, reflectedLight );
+
+	}
+	#pragma unroll_loop_end
+
+#endif
+
+#if ( NUM_RECT_AREA_LIGHTS > 0 ) && defined( RE_Direct_RectArea )
+
+	RectAreaLight rectAreaLight;
+
+	#pragma unroll_loop_start
+	for ( int i = 0; i < NUM_RECT_AREA_LIGHTS; i ++ ) {
+
+		rectAreaLight = rectAreaLights[ i ];
+		RE_Direct_RectArea( rectAreaLight, geometry, material, reflectedLight );
+
+	}
+	#pragma unroll_loop_end
+
+#endif
+
+#if defined( RE_IndirectDiffuse )
+
+	vec3 iblIrradiance = vec3( 0.0 );
+
+	vec3 irradiance = getAmbientLightIrradiance( ambientLightColor );
+
+	irradiance += getLightProbeIrradiance( lightProbe, geometry );
+
+	#if ( NUM_HEMI_LIGHTS > 0 )
+
+		#pragma unroll_loop_start
+		for ( int i = 0; i < NUM_HEMI_LIGHTS; i ++ ) {
+
+			irradiance += getHemisphereLightIrradiance( hemisphereLights[ i ], geometry );
+
+		}
+		#pragma unroll_loop_end
+
+	#endif
+
+#endif
+
+#if defined( RE_IndirectSpecular )
+
+	vec3 radiance = vec3( 0.0 );
+	vec3 clearcoatRadiance = vec3( 0.0 );
+
+#endif
+`,
+		lights_pars_begin:
+	/* glsl */
+	`
+#if defined( USE_CSM ) && defined( CSM_CASCADES )
+uniform vec2 CSM_cascades[CSM_CASCADES];
+uniform float cameraNear;
+uniform float shadowFar;
+#endif
+	` + THREE.ShaderChunk.lights_pars_begin
+	};
+
+	THREE.CSMShader = CSMShader;
+
+} )();

+ 133 - 0
examples/js/csm/Frustum.js

@@ -0,0 +1,133 @@
+( function () {
+
+	const inverseProjectionMatrix = new THREE.Matrix4();
+
+	class Frustum {
+
+		constructor( data ) {
+
+			data = data || {};
+			this.vertices = {
+				near: [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ],
+				far: [ new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3(), new THREE.Vector3() ]
+			};
+
+			if ( data.projectionMatrix !== undefined ) {
+
+				this.setFromProjectionMatrix( data.projectionMatrix, data.maxFar || 10000 );
+
+			}
+
+		}
+
+		setFromProjectionMatrix( projectionMatrix, maxFar ) {
+
+			const isOrthographic = projectionMatrix.elements[ 2 * 4 + 3 ] === 0;
+			inverseProjectionMatrix.copy( projectionMatrix ).invert(); // 3 --- 0	vertices.near/far order
+			// |		 |
+			// 2 --- 1
+			// clip space spans from [-1, 1]
+
+			this.vertices.near[ 0 ].set( 1, 1, - 1 );
+			this.vertices.near[ 1 ].set( 1, - 1, - 1 );
+			this.vertices.near[ 2 ].set( - 1, - 1, - 1 );
+			this.vertices.near[ 3 ].set( - 1, 1, - 1 );
+			this.vertices.near.forEach( function ( v ) {
+
+				v.applyMatrix4( inverseProjectionMatrix );
+
+			} );
+			this.vertices.far[ 0 ].set( 1, 1, 1 );
+			this.vertices.far[ 1 ].set( 1, - 1, 1 );
+			this.vertices.far[ 2 ].set( - 1, - 1, 1 );
+			this.vertices.far[ 3 ].set( - 1, 1, 1 );
+			this.vertices.far.forEach( function ( v ) {
+
+				v.applyMatrix4( inverseProjectionMatrix );
+				const absZ = Math.abs( v.z );
+
+				if ( isOrthographic ) {
+
+					v.z *= Math.min( maxFar / absZ, 1.0 );
+
+				} else {
+
+					v.multiplyScalar( Math.min( maxFar / absZ, 1.0 ) );
+
+				}
+
+			} );
+			return this.vertices;
+
+		}
+
+		split( breaks, target ) {
+
+			while ( breaks.length > target.length ) {
+
+				target.push( new Frustum() );
+
+			}
+
+			target.length = breaks.length;
+
+			for ( let i = 0; i < breaks.length; i ++ ) {
+
+				const cascade = target[ i ];
+
+				if ( i === 0 ) {
+
+					for ( let j = 0; j < 4; j ++ ) {
+
+						cascade.vertices.near[ j ].copy( this.vertices.near[ j ] );
+
+					}
+
+				} else {
+
+					for ( let j = 0; j < 4; j ++ ) {
+
+						cascade.vertices.near[ j ].lerpVectors( this.vertices.near[ j ], this.vertices.far[ j ], breaks[ i - 1 ] );
+
+					}
+
+				}
+
+				if ( i === breaks - 1 ) {
+
+					for ( let j = 0; j < 4; j ++ ) {
+
+						cascade.vertices.far[ j ].copy( this.vertices.far[ j ] );
+
+					}
+
+				} else {
+
+					for ( let j = 0; j < 4; j ++ ) {
+
+						cascade.vertices.far[ j ].lerpVectors( this.vertices.near[ j ], this.vertices.far[ j ], breaks[ i ] );
+
+					}
+
+				}
+
+			}
+
+		}
+
+		toSpace( cameraMatrix, target ) {
+
+			for ( var i = 0; i < 4; i ++ ) {
+
+				target.vertices.near[ i ].copy( this.vertices.near[ i ] ).applyMatrix4( cameraMatrix );
+				target.vertices.far[ i ].copy( this.vertices.far[ i ] ).applyMatrix4( cameraMatrix );
+
+			}
+
+		}
+
+	}
+
+	THREE.Frustum = Frustum;
+
+} )();

+ 229 - 291
examples/js/curves/CurveExtras.js

@@ -1,4 +1,6 @@
-/**
+( function () {
+
+	/**
  * A bunch of parametric curves
  *
  * Formulas collected from various sources
@@ -10,415 +12,351 @@
  * https://prideout.net/blog/old/blog/index.html@p=44.html
  */
 
-THREE.Curves = ( function () {
-
-	// GrannyKnot
-
-	function GrannyKnot() {
-
-		THREE.Curve.call( this );
-
-	}
-
-	GrannyKnot.prototype = Object.create( THREE.Curve.prototype );
-	GrannyKnot.prototype.constructor = GrannyKnot;
-
-	GrannyKnot.prototype.getPoint = function ( t, optionalTarget ) {
-
-		var point = optionalTarget || new THREE.Vector3();
-
-		t = 2 * Math.PI * t;
-
-		var x = - 0.22 * Math.cos( t ) - 1.28 * Math.sin( t ) - 0.44 * Math.cos( 3 * t ) - 0.78 * Math.sin( 3 * t );
-		var y = - 0.1 * Math.cos( 2 * t ) - 0.27 * Math.sin( 2 * t ) + 0.38 * Math.cos( 4 * t ) + 0.46 * Math.sin( 4 * t );
-		var z = 0.7 * Math.cos( 3 * t ) - 0.4 * Math.sin( 3 * t );
-
-		return point.set( x, y, z ).multiplyScalar( 20 );
-
-	};
-
-	// HeartCurve
-
-	function HeartCurve( scale ) {
-
-		THREE.Curve.call( this );
-
-		this.scale = ( scale === undefined ) ? 5 : scale;
-
-	}
-
-	HeartCurve.prototype = Object.create( THREE.Curve.prototype );
-	HeartCurve.prototype.constructor = HeartCurve;
-
-	HeartCurve.prototype.getPoint = function ( t, optionalTarget ) {
-
-		var point = optionalTarget || new THREE.Vector3();
-
-		t *= 2 * Math.PI;
-
-		var x = 16 * Math.pow( Math.sin( t ), 3 );
-		var y = 13 * Math.cos( t ) - 5 * Math.cos( 2 * t ) - 2 * Math.cos( 3 * t ) - Math.cos( 4 * t );
-		var z = 0;
-
-		return point.set( x, y, z ).multiplyScalar( this.scale );
-
-	};
-
-	// Viviani's Curve
-
-	function VivianiCurve( scale ) {
-
-		THREE.Curve.call( this );
-
-		this.scale = ( scale === undefined ) ? 70 : scale;
-
-	}
-
-	VivianiCurve.prototype = Object.create( THREE.Curve.prototype );
-	VivianiCurve.prototype.constructor = VivianiCurve;
-
-	VivianiCurve.prototype.getPoint = function ( t, optionalTarget ) {
-
-		var point = optionalTarget || new THREE.Vector3();
-
-		t = t * 4 * Math.PI; // normalized to 0..1
-		var a = this.scale / 2;
-
-		var x = a * ( 1 + Math.cos( t ) );
-		var y = a * Math.sin( t );
-		var z = 2 * a * Math.sin( t / 2 );
-
-		return point.set( x, y, z );
-
-	};
-
-	// KnotCurve
-
-	function KnotCurve() {
-
-		THREE.Curve.call( this );
-
-	}
-
-	KnotCurve.prototype = Object.create( THREE.Curve.prototype );
-	KnotCurve.prototype.constructor = KnotCurve;
-
-	KnotCurve.prototype.getPoint = function ( t, optionalTarget ) {
-
-		var point = optionalTarget || new THREE.Vector3();
-
-		t *= 2 * Math.PI;
-
-		var R = 10;
-		var s = 50;
-
-		var x = s * Math.sin( t );
-		var y = Math.cos( t ) * ( R + s * Math.cos( t ) );
-		var z = Math.sin( t ) * ( R + s * Math.cos( t ) );
-
-		return point.set( x, y, z );
-
-	};
-
-	// HelixCurve
-
-	function HelixCurve() {
-
-		THREE.Curve.call( this );
-
-	}
-
-	HelixCurve.prototype = Object.create( THREE.Curve.prototype );
-	HelixCurve.prototype.constructor = HelixCurve;
-
-	HelixCurve.prototype.getPoint = function ( t, optionalTarget ) {
+	var Curves = function () {
 
-		var point = optionalTarget || new THREE.Vector3();
+		// GrannyKnot
+		function GrannyKnot() {
 
-		var a = 30; // radius
-		var b = 150; // height
+			THREE.Curve.call( this );
 
-		var t2 = 2 * Math.PI * t * b / 30;
+		}
 
-		var x = Math.cos( t2 ) * a;
-		var y = Math.sin( t2 ) * a;
-		var z = b * t;
+		GrannyKnot.prototype = Object.create( THREE.Curve.prototype );
+		GrannyKnot.prototype.constructor = GrannyKnot;
 
-		return point.set( x, y, z );
+		GrannyKnot.prototype.getPoint = function ( t, optionalTarget ) {
 
-	};
+			var point = optionalTarget || new THREE.Vector3();
+			t = 2 * Math.PI * t;
+			var x = - 0.22 * Math.cos( t ) - 1.28 * Math.sin( t ) - 0.44 * Math.cos( 3 * t ) - 0.78 * Math.sin( 3 * t );
+			var y = - 0.1 * Math.cos( 2 * t ) - 0.27 * Math.sin( 2 * t ) + 0.38 * Math.cos( 4 * t ) + 0.46 * Math.sin( 4 * t );
+			var z = 0.7 * Math.cos( 3 * t ) - 0.4 * Math.sin( 3 * t );
+			return point.set( x, y, z ).multiplyScalar( 20 );
 
-	// TrefoilKnot
+		}; // HeartCurve
 
-	function TrefoilKnot( scale ) {
 
-		THREE.Curve.call( this );
+		function HeartCurve( scale ) {
 
-		this.scale = ( scale === undefined ) ? 10 : scale;
+			THREE.Curve.call( this );
+			this.scale = scale === undefined ? 5 : scale;
 
-	}
+		}
 
-	TrefoilKnot.prototype = Object.create( THREE.Curve.prototype );
-	TrefoilKnot.prototype.constructor = TrefoilKnot;
+		HeartCurve.prototype = Object.create( THREE.Curve.prototype );
+		HeartCurve.prototype.constructor = HeartCurve;
 
-	TrefoilKnot.prototype.getPoint = function ( t, optionalTarget ) {
+		HeartCurve.prototype.getPoint = function ( t, optionalTarget ) {
 
-		var point = optionalTarget || new THREE.Vector3();
+			var point = optionalTarget || new THREE.Vector3();
+			t *= 2 * Math.PI;
+			var x = 16 * Math.pow( Math.sin( t ), 3 );
+			var y = 13 * Math.cos( t ) - 5 * Math.cos( 2 * t ) - 2 * Math.cos( 3 * t ) - Math.cos( 4 * t );
+			var z = 0;
+			return point.set( x, y, z ).multiplyScalar( this.scale );
 
-		t *= Math.PI * 2;
+		}; // Viviani's THREE.Curve
 
-		var x = ( 2 + Math.cos( 3 * t ) ) * Math.cos( 2 * t );
-		var y = ( 2 + Math.cos( 3 * t ) ) * Math.sin( 2 * t );
-		var z = Math.sin( 3 * t );
 
-		return point.set( x, y, z ).multiplyScalar( this.scale );
+		function VivianiCurve( scale ) {
 
-	};
+			THREE.Curve.call( this );
+			this.scale = scale === undefined ? 70 : scale;
 
-	// TorusKnot
+		}
 
-	function TorusKnot( scale ) {
+		VivianiCurve.prototype = Object.create( THREE.Curve.prototype );
+		VivianiCurve.prototype.constructor = VivianiCurve;
 
-		THREE.Curve.call( this );
+		VivianiCurve.prototype.getPoint = function ( t, optionalTarget ) {
 
-		this.scale = ( scale === undefined ) ? 10 : scale;
+			var point = optionalTarget || new THREE.Vector3();
+			t = t * 4 * Math.PI; // normalized to 0..1
 
-	}
+			var a = this.scale / 2;
+			var x = a * ( 1 + Math.cos( t ) );
+			var y = a * Math.sin( t );
+			var z = 2 * a * Math.sin( t / 2 );
+			return point.set( x, y, z );
 
-	TorusKnot.prototype = Object.create( THREE.Curve.prototype );
-	TorusKnot.prototype.constructor = TorusKnot;
+		}; // KnotCurve
 
-	TorusKnot.prototype.getPoint = function ( t, optionalTarget ) {
 
-		var point = optionalTarget || new THREE.Vector3();
+		function KnotCurve() {
 
-		var p = 3;
-		var q = 4;
+			THREE.Curve.call( this );
 
-		t *= Math.PI * 2;
+		}
 
-		var x = ( 2 + Math.cos( q * t ) ) * Math.cos( p * t );
-		var y = ( 2 + Math.cos( q * t ) ) * Math.sin( p * t );
-		var z = Math.sin( q * t );
+		KnotCurve.prototype = Object.create( THREE.Curve.prototype );
+		KnotCurve.prototype.constructor = KnotCurve;
 
-		return point.set( x, y, z ).multiplyScalar( this.scale );
+		KnotCurve.prototype.getPoint = function ( t, optionalTarget ) {
 
-	};
+			var point = optionalTarget || new THREE.Vector3();
+			t *= 2 * Math.PI;
+			var R = 10;
+			var s = 50;
+			var x = s * Math.sin( t );
+			var y = Math.cos( t ) * ( R + s * Math.cos( t ) );
+			var z = Math.sin( t ) * ( R + s * Math.cos( t ) );
+			return point.set( x, y, z );
 
-	// CinquefoilKnot
+		}; // HelixCurve
 
-	function CinquefoilKnot( scale ) {
 
-		THREE.Curve.call( this );
+		function HelixCurve() {
 
-		this.scale = ( scale === undefined ) ? 10 : scale;
+			THREE.Curve.call( this );
 
-	}
+		}
 
-	CinquefoilKnot.prototype = Object.create( THREE.Curve.prototype );
-	CinquefoilKnot.prototype.constructor = CinquefoilKnot;
+		HelixCurve.prototype = Object.create( THREE.Curve.prototype );
+		HelixCurve.prototype.constructor = HelixCurve;
 
-	CinquefoilKnot.prototype.getPoint = function ( t, optionalTarget ) {
+		HelixCurve.prototype.getPoint = function ( t, optionalTarget ) {
 
-		var point = optionalTarget || new THREE.Vector3();
+			var point = optionalTarget || new THREE.Vector3();
+			var a = 30; // radius
 
-		var p = 2;
-		var q = 5;
+			var b = 150; // height
 
-		t *= Math.PI * 2;
+			var t2 = 2 * Math.PI * t * b / 30;
+			var x = Math.cos( t2 ) * a;
+			var y = Math.sin( t2 ) * a;
+			var z = b * t;
+			return point.set( x, y, z );
 
-		var x = ( 2 + Math.cos( q * t ) ) * Math.cos( p * t );
-		var y = ( 2 + Math.cos( q * t ) ) * Math.sin( p * t );
-		var z = Math.sin( q * t );
+		}; // TrefoilKnot
 
-		return point.set( x, y, z ).multiplyScalar( this.scale );
 
-	};
+		function TrefoilKnot( scale ) {
 
-	// TrefoilPolynomialKnot
+			THREE.Curve.call( this );
+			this.scale = scale === undefined ? 10 : scale;
 
-	function TrefoilPolynomialKnot( scale ) {
+		}
 
-		THREE.Curve.call( this );
+		TrefoilKnot.prototype = Object.create( THREE.Curve.prototype );
+		TrefoilKnot.prototype.constructor = TrefoilKnot;
 
-		this.scale = ( scale === undefined ) ? 10 : scale;
+		TrefoilKnot.prototype.getPoint = function ( t, optionalTarget ) {
 
-	}
+			var point = optionalTarget || new THREE.Vector3();
+			t *= Math.PI * 2;
+			var x = ( 2 + Math.cos( 3 * t ) ) * Math.cos( 2 * t );
+			var y = ( 2 + Math.cos( 3 * t ) ) * Math.sin( 2 * t );
+			var z = Math.sin( 3 * t );
+			return point.set( x, y, z ).multiplyScalar( this.scale );
 
-	TrefoilPolynomialKnot.prototype = Object.create( THREE.Curve.prototype );
-	TrefoilPolynomialKnot.prototype.constructor = TrefoilPolynomialKnot;
+		}; // TorusKnot
 
-	TrefoilPolynomialKnot.prototype.getPoint = function ( t, optionalTarget ) {
 
-		var point = optionalTarget || new THREE.Vector3();
+		function TorusKnot( scale ) {
 
-		t = t * 4 - 2;
+			THREE.Curve.call( this );
+			this.scale = scale === undefined ? 10 : scale;
 
-		var x = Math.pow( t, 3 ) - 3 * t;
-		var y = Math.pow( t, 4 ) - 4 * t * t;
-		var z = 1 / 5 * Math.pow( t, 5 ) - 2 * t;
+		}
 
-		return point.set( x, y, z ).multiplyScalar( this.scale );
+		TorusKnot.prototype = Object.create( THREE.Curve.prototype );
+		TorusKnot.prototype.constructor = TorusKnot;
 
-	};
+		TorusKnot.prototype.getPoint = function ( t, optionalTarget ) {
 
-	var scaleTo = function ( x, y, t ) {
+			var point = optionalTarget || new THREE.Vector3();
+			var p = 3;
+			var q = 4;
+			t *= Math.PI * 2;
+			var x = ( 2 + Math.cos( q * t ) ) * Math.cos( p * t );
+			var y = ( 2 + Math.cos( q * t ) ) * Math.sin( p * t );
+			var z = Math.sin( q * t );
+			return point.set( x, y, z ).multiplyScalar( this.scale );
 
-		var r = y - x;
-		return t * r + x;
+		}; // CinquefoilKnot
 
-	};
 
-	// FigureEightPolynomialKnot
+		function CinquefoilKnot( scale ) {
 
-	function FigureEightPolynomialKnot( scale ) {
+			THREE.Curve.call( this );
+			this.scale = scale === undefined ? 10 : scale;
 
-		THREE.Curve.call( this );
+		}
 
-		this.scale = ( scale === undefined ) ? 1 : scale;
+		CinquefoilKnot.prototype = Object.create( THREE.Curve.prototype );
+		CinquefoilKnot.prototype.constructor = CinquefoilKnot;
 
-	}
+		CinquefoilKnot.prototype.getPoint = function ( t, optionalTarget ) {
 
-	FigureEightPolynomialKnot.prototype = Object.create( THREE.Curve.prototype );
-	FigureEightPolynomialKnot.prototype.constructor = FigureEightPolynomialKnot;
+			var point = optionalTarget || new THREE.Vector3();
+			var p = 2;
+			var q = 5;
+			t *= Math.PI * 2;
+			var x = ( 2 + Math.cos( q * t ) ) * Math.cos( p * t );
+			var y = ( 2 + Math.cos( q * t ) ) * Math.sin( p * t );
+			var z = Math.sin( q * t );
+			return point.set( x, y, z ).multiplyScalar( this.scale );
 
-	FigureEightPolynomialKnot.prototype.getPoint = function ( t, optionalTarget ) {
+		}; // TrefoilPolynomialKnot
 
-		var point = optionalTarget || new THREE.Vector3();
 
-		t = scaleTo( - 4, 4, t );
+		function TrefoilPolynomialKnot( scale ) {
 
-		var x = 2 / 5 * t * ( t * t - 7 ) * ( t * t - 10 );
-		var y = Math.pow( t, 4 ) - 13 * t * t;
-		var z = 1 / 10 * t * ( t * t - 4 ) * ( t * t - 9 ) * ( t * t - 12 );
+			THREE.Curve.call( this );
+			this.scale = scale === undefined ? 10 : scale;
 
-		return point.set( x, y, z ).multiplyScalar( this.scale );
+		}
 
-	};
+		TrefoilPolynomialKnot.prototype = Object.create( THREE.Curve.prototype );
+		TrefoilPolynomialKnot.prototype.constructor = TrefoilPolynomialKnot;
 
-	// DecoratedTorusKnot4a
+		TrefoilPolynomialKnot.prototype.getPoint = function ( t, optionalTarget ) {
 
-	function DecoratedTorusKnot4a( scale ) {
+			var point = optionalTarget || new THREE.Vector3();
+			t = t * 4 - 2;
+			var x = Math.pow( t, 3 ) - 3 * t;
+			var y = Math.pow( t, 4 ) - 4 * t * t;
+			var z = 1 / 5 * Math.pow( t, 5 ) - 2 * t;
+			return point.set( x, y, z ).multiplyScalar( this.scale );
 
-		THREE.Curve.call( this );
+		};
 
-		this.scale = ( scale === undefined ) ? 40 : scale;
+		var scaleTo = function ( x, y, t ) {
 
-	}
+			var r = y - x;
+			return t * r + x;
 
-	DecoratedTorusKnot4a.prototype = Object.create( THREE.Curve.prototype );
-	DecoratedTorusKnot4a.prototype.constructor = DecoratedTorusKnot4a;
+		}; // FigureEightPolynomialKnot
 
-	DecoratedTorusKnot4a.prototype.getPoint = function ( t, optionalTarget ) {
 
-		var point = optionalTarget || new THREE.Vector3();
+		function FigureEightPolynomialKnot( scale ) {
 
-		t *= Math.PI * 2;
+			THREE.Curve.call( this );
+			this.scale = scale === undefined ? 1 : scale;
 
-		var x = Math.cos( 2 * t ) * ( 1 + 0.6 * ( Math.cos( 5 * t ) + 0.75 * Math.cos( 10 * t ) ) );
-		var y = Math.sin( 2 * t ) * ( 1 + 0.6 * ( Math.cos( 5 * t ) + 0.75 * Math.cos( 10 * t ) ) );
-		var z = 0.35 * Math.sin( 5 * t );
+		}
 
-		return point.set( x, y, z ).multiplyScalar( this.scale );
+		FigureEightPolynomialKnot.prototype = Object.create( THREE.Curve.prototype );
+		FigureEightPolynomialKnot.prototype.constructor = FigureEightPolynomialKnot;
 
-	};
+		FigureEightPolynomialKnot.prototype.getPoint = function ( t, optionalTarget ) {
 
-	// DecoratedTorusKnot4b
+			var point = optionalTarget || new THREE.Vector3();
+			t = scaleTo( - 4, 4, t );
+			var x = 2 / 5 * t * ( t * t - 7 ) * ( t * t - 10 );
+			var y = Math.pow( t, 4 ) - 13 * t * t;
+			var z = 1 / 10 * t * ( t * t - 4 ) * ( t * t - 9 ) * ( t * t - 12 );
+			return point.set( x, y, z ).multiplyScalar( this.scale );
 
-	function DecoratedTorusKnot4b( scale ) {
+		}; // DecoratedTorusKnot4a
 
-		THREE.Curve.call( this );
 
-		this.scale = ( scale === undefined ) ? 40 : scale;
+		function DecoratedTorusKnot4a( scale ) {
 
-	}
+			THREE.Curve.call( this );
+			this.scale = scale === undefined ? 40 : scale;
 
-	DecoratedTorusKnot4b.prototype = Object.create( THREE.Curve.prototype );
-	DecoratedTorusKnot4b.prototype.constructor = DecoratedTorusKnot4b;
+		}
 
-	DecoratedTorusKnot4b.prototype.getPoint = function ( t, optionalTarget ) {
+		DecoratedTorusKnot4a.prototype = Object.create( THREE.Curve.prototype );
+		DecoratedTorusKnot4a.prototype.constructor = DecoratedTorusKnot4a;
 
-		var point = optionalTarget || new THREE.Vector3();
+		DecoratedTorusKnot4a.prototype.getPoint = function ( t, optionalTarget ) {
 
-		var fi = t * Math.PI * 2;
+			var point = optionalTarget || new THREE.Vector3();
+			t *= Math.PI * 2;
+			var x = Math.cos( 2 * t ) * ( 1 + 0.6 * ( Math.cos( 5 * t ) + 0.75 * Math.cos( 10 * t ) ) );
+			var y = Math.sin( 2 * t ) * ( 1 + 0.6 * ( Math.cos( 5 * t ) + 0.75 * Math.cos( 10 * t ) ) );
+			var z = 0.35 * Math.sin( 5 * t );
+			return point.set( x, y, z ).multiplyScalar( this.scale );
 
-		var x = Math.cos( 2 * fi ) * ( 1 + 0.45 * Math.cos( 3 * fi ) + 0.4 * Math.cos( 9 * fi ) );
-		var y = Math.sin( 2 * fi ) * ( 1 + 0.45 * Math.cos( 3 * fi ) + 0.4 * Math.cos( 9 * fi ) );
-		var z = 0.2 * Math.sin( 9 * fi );
+		}; // DecoratedTorusKnot4b
 
-		return point.set( x, y, z ).multiplyScalar( this.scale );
 
-	};
+		function DecoratedTorusKnot4b( scale ) {
 
-	// DecoratedTorusKnot5a
+			THREE.Curve.call( this );
+			this.scale = scale === undefined ? 40 : scale;
 
-	function DecoratedTorusKnot5a( scale ) {
+		}
 
-		THREE.Curve.call( this );
+		DecoratedTorusKnot4b.prototype = Object.create( THREE.Curve.prototype );
+		DecoratedTorusKnot4b.prototype.constructor = DecoratedTorusKnot4b;
 
-		this.scale = ( scale === undefined ) ? 40 : scale;
+		DecoratedTorusKnot4b.prototype.getPoint = function ( t, optionalTarget ) {
 
-	}
+			var point = optionalTarget || new THREE.Vector3();
+			var fi = t * Math.PI * 2;
+			var x = Math.cos( 2 * fi ) * ( 1 + 0.45 * Math.cos( 3 * fi ) + 0.4 * Math.cos( 9 * fi ) );
+			var y = Math.sin( 2 * fi ) * ( 1 + 0.45 * Math.cos( 3 * fi ) + 0.4 * Math.cos( 9 * fi ) );
+			var z = 0.2 * Math.sin( 9 * fi );
+			return point.set( x, y, z ).multiplyScalar( this.scale );
 
-	DecoratedTorusKnot5a.prototype = Object.create( THREE.Curve.prototype );
-	DecoratedTorusKnot5a.prototype.constructor = DecoratedTorusKnot5a;
+		}; // DecoratedTorusKnot5a
 
-	DecoratedTorusKnot5a.prototype.getPoint = function ( t, optionalTarget ) {
 
-		var point = optionalTarget || new THREE.Vector3();
+		function DecoratedTorusKnot5a( scale ) {
 
-		var fi = t * Math.PI * 2;
+			THREE.Curve.call( this );
+			this.scale = scale === undefined ? 40 : scale;
 
-		var x = Math.cos( 3 * fi ) * ( 1 + 0.3 * Math.cos( 5 * fi ) + 0.5 * Math.cos( 10 * fi ) );
-		var y = Math.sin( 3 * fi ) * ( 1 + 0.3 * Math.cos( 5 * fi ) + 0.5 * Math.cos( 10 * fi ) );
-		var z = 0.2 * Math.sin( 20 * fi );
+		}
 
-		return point.set( x, y, z ).multiplyScalar( this.scale );
+		DecoratedTorusKnot5a.prototype = Object.create( THREE.Curve.prototype );
+		DecoratedTorusKnot5a.prototype.constructor = DecoratedTorusKnot5a;
 
-	};
+		DecoratedTorusKnot5a.prototype.getPoint = function ( t, optionalTarget ) {
 
-	// DecoratedTorusKnot5c
+			var point = optionalTarget || new THREE.Vector3();
+			var fi = t * Math.PI * 2;
+			var x = Math.cos( 3 * fi ) * ( 1 + 0.3 * Math.cos( 5 * fi ) + 0.5 * Math.cos( 10 * fi ) );
+			var y = Math.sin( 3 * fi ) * ( 1 + 0.3 * Math.cos( 5 * fi ) + 0.5 * Math.cos( 10 * fi ) );
+			var z = 0.2 * Math.sin( 20 * fi );
+			return point.set( x, y, z ).multiplyScalar( this.scale );
 
-	function DecoratedTorusKnot5c( scale ) {
+		}; // DecoratedTorusKnot5c
 
-		THREE.Curve.call( this );
 
-		this.scale = ( scale === undefined ) ? 40 : scale;
+		function DecoratedTorusKnot5c( scale ) {
 
-	}
+			THREE.Curve.call( this );
+			this.scale = scale === undefined ? 40 : scale;
 
-	DecoratedTorusKnot5c.prototype = Object.create( THREE.Curve.prototype );
-	DecoratedTorusKnot5c.prototype.constructor = DecoratedTorusKnot5c;
+		}
 
-	DecoratedTorusKnot5c.prototype.getPoint = function ( t, optionalTarget ) {
+		DecoratedTorusKnot5c.prototype = Object.create( THREE.Curve.prototype );
+		DecoratedTorusKnot5c.prototype.constructor = DecoratedTorusKnot5c;
 
-		var point = optionalTarget || new THREE.Vector3();
+		DecoratedTorusKnot5c.prototype.getPoint = function ( t, optionalTarget ) {
 
-		var fi = t * Math.PI * 2;
+			var point = optionalTarget || new THREE.Vector3();
+			var fi = t * Math.PI * 2;
+			var x = Math.cos( 4 * fi ) * ( 1 + 0.5 * ( Math.cos( 5 * fi ) + 0.4 * Math.cos( 20 * fi ) ) );
+			var y = Math.sin( 4 * fi ) * ( 1 + 0.5 * ( Math.cos( 5 * fi ) + 0.4 * Math.cos( 20 * fi ) ) );
+			var z = 0.35 * Math.sin( 15 * fi );
+			return point.set( x, y, z ).multiplyScalar( this.scale );
 
-		var x = Math.cos( 4 * fi ) * ( 1 + 0.5 * ( Math.cos( 5 * fi ) + 0.4 * Math.cos( 20 * fi ) ) );
-		var y = Math.sin( 4 * fi ) * ( 1 + 0.5 * ( Math.cos( 5 * fi ) + 0.4 * Math.cos( 20 * fi ) ) );
-		var z = 0.35 * Math.sin( 15 * fi );
+		};
 
-		return point.set( x, y, z ).multiplyScalar( this.scale );
+		return {
+			GrannyKnot: GrannyKnot,
+			HeartCurve: HeartCurve,
+			VivianiCurve: VivianiCurve,
+			KnotCurve: KnotCurve,
+			HelixCurve: HelixCurve,
+			TrefoilKnot: TrefoilKnot,
+			TorusKnot: TorusKnot,
+			CinquefoilKnot: CinquefoilKnot,
+			TrefoilPolynomialKnot: TrefoilPolynomialKnot,
+			FigureEightPolynomialKnot: FigureEightPolynomialKnot,
+			DecoratedTorusKnot4a: DecoratedTorusKnot4a,
+			DecoratedTorusKnot4b: DecoratedTorusKnot4b,
+			DecoratedTorusKnot5a: DecoratedTorusKnot5a,
+			DecoratedTorusKnot5c: DecoratedTorusKnot5c
+		};
 
-	};
+	}();
 
-	return {
-		GrannyKnot: GrannyKnot,
-		HeartCurve: HeartCurve,
-		VivianiCurve: VivianiCurve,
-		KnotCurve: KnotCurve,
-		HelixCurve: HelixCurve,
-		TrefoilKnot: TrefoilKnot,
-		TorusKnot: TorusKnot,
-		CinquefoilKnot: CinquefoilKnot,
-		TrefoilPolynomialKnot: TrefoilPolynomialKnot,
-		FigureEightPolynomialKnot: FigureEightPolynomialKnot,
-		DecoratedTorusKnot4a: DecoratedTorusKnot4a,
-		DecoratedTorusKnot4b: DecoratedTorusKnot4b,
-		DecoratedTorusKnot5a: DecoratedTorusKnot5a,
-		DecoratedTorusKnot5c: DecoratedTorusKnot5c
-	};
+	THREE.Curves = Curves;
 
 } )();

+ 47 - 39
examples/js/curves/NURBSCurve.js

@@ -1,66 +1,74 @@
-/**
+( function () {
+
+	/**
  * NURBS curve object
  *
- * Derives from Curve, overriding getPoint and getTangent.
+ * Derives from THREE.Curve, overriding getPoint and getTangent.
  *
  * Implementation is based on (x, y [, z=0 [, w=1]]) control points with w=weight.
  *
  **/
 
-THREE.NURBSCurve = function ( degree, knots /* array of reals */, controlPoints /* array of Vector(2|3|4) */, startKnot /* index in knots */, endKnot /* index in knots */ ) {
-
-	THREE.Curve.call( this );
-
-	this.degree = degree;
-	this.knots = knots;
-	this.controlPoints = [];
-	// Used by periodic NURBS to remove hidden spans
-	this.startKnot = startKnot || 0;
-	this.endKnot = endKnot || ( this.knots.length - 1 );
-	for ( var i = 0; i < controlPoints.length; ++ i ) {
-
-		// ensure Vector4 for control points
-		var point = controlPoints[ i ];
-		this.controlPoints[ i ] = new THREE.Vector4( point.x, point.y, point.z, point.w );
+	var NURBSCurve = function ( degree, knots
+		/* array of reals */
+		, controlPoints
+		/* array of Vector(2|3|4) */
+		, startKnot
+		/* index in knots */
+		, endKnot
+		/* index in knots */
+	) {
 
-	}
+		THREE.Curve.call( this );
+		this.degree = degree;
+		this.knots = knots;
+		this.controlPoints = []; // Used by periodic NURBS to remove hidden spans
 
-};
+		this.startKnot = startKnot || 0;
+		this.endKnot = endKnot || this.knots.length - 1;
 
+		for ( var i = 0; i < controlPoints.length; ++ i ) {
 
-THREE.NURBSCurve.prototype = Object.create( THREE.Curve.prototype );
-THREE.NURBSCurve.prototype.constructor = THREE.NURBSCurve;
+			// ensure THREE.Vector4 for control points
+			var point = controlPoints[ i ];
+			this.controlPoints[ i ] = new THREE.Vector4( point.x, point.y, point.z, point.w );
 
+		}
 
-THREE.NURBSCurve.prototype.getPoint = function ( t, optionalTarget ) {
+	};
 
-	var point = optionalTarget || new THREE.Vector3();
+	NURBSCurve.prototype = Object.create( THREE.Curve.prototype );
+	NURBSCurve.prototype.constructor = NURBSCurve;
 
-	var u = this.knots[ this.startKnot ] + t * ( this.knots[ this.endKnot ] - this.knots[ this.startKnot ] ); // linear mapping t->u
+	NURBSCurve.prototype.getPoint = function ( t, optionalTarget ) {
 
-	// following results in (wx, wy, wz, w) homogeneous point
-	var hpoint = THREE.NURBSUtils.calcBSplinePoint( this.degree, this.knots, this.controlPoints, u );
+		var point = optionalTarget || new THREE.Vector3();
+		var u = this.knots[ this.startKnot ] + t * ( this.knots[ this.endKnot ] - this.knots[ this.startKnot ] ); // linear mapping t->u
+		// following results in (wx, wy, wz, w) homogeneous point
 
-	if ( hpoint.w != 1.0 ) {
+		var hpoint = THREE.NURBSUtils.calcBSplinePoint( this.degree, this.knots, this.controlPoints, u );
 
-		// project to 3D space: (wx, wy, wz, w) -> (x, y, z, 1)
-		hpoint.divideScalar( hpoint.w );
+		if ( hpoint.w != 1.0 ) {
 
-	}
+			// project to 3D space: (wx, wy, wz, w) -> (x, y, z, 1)
+			hpoint.divideScalar( hpoint.w );
 
-	return point.set( hpoint.x, hpoint.y, hpoint.z );
+		}
 
-};
+		return point.set( hpoint.x, hpoint.y, hpoint.z );
 
+	};
 
-THREE.NURBSCurve.prototype.getTangent = function ( t, optionalTarget ) {
+	NURBSCurve.prototype.getTangent = function ( t, optionalTarget ) {
 
-	var tangent = optionalTarget || new THREE.Vector3();
+		var tangent = optionalTarget || new THREE.Vector3();
+		var u = this.knots[ 0 ] + t * ( this.knots[ this.knots.length - 1 ] - this.knots[ 0 ] );
+		var ders = THREE.NURBSUtils.calcNURBSDerivatives( this.degree, this.knots, this.controlPoints, u, 1 );
+		tangent.copy( ders[ 1 ] ).normalize();
+		return tangent;
 
-	var u = this.knots[ 0 ] + t * ( this.knots[ this.knots.length - 1 ] - this.knots[ 0 ] );
-	var ders = THREE.NURBSUtils.calcNURBSDerivatives( this.degree, this.knots, this.controlPoints, u, 1 );
-	tangent.copy( ders[ 1 ] ).normalize();
+	};
 
-	return tangent;
+	THREE.NURBSCurve = NURBSCurve;
 
-};
+} )();

+ 33 - 26
examples/js/curves/NURBSSurface.js

@@ -1,46 +1,53 @@
-/**
+( function () {
+
+	/**
  * NURBS surface object
  *
  * Implementation is based on (x, y [, z=0 [, w=1]]) control points with w=weight.
  **/
 
-THREE.NURBSSurface = function ( degree1, degree2, knots1, knots2 /* arrays of reals */, controlPoints /* array^2 of Vector(2|3|4) */ ) {
+	var NURBSSurface = function ( degree1, degree2, knots1, knots2
+		/* arrays of reals */
+		, controlPoints
+		/* array^2 of Vector(2|3|4) */
+	) {
 
-	this.degree1 = degree1;
-	this.degree2 = degree2;
-	this.knots1 = knots1;
-	this.knots2 = knots2;
-	this.controlPoints = [];
+		this.degree1 = degree1;
+		this.degree2 = degree2;
+		this.knots1 = knots1;
+		this.knots2 = knots2;
+		this.controlPoints = [];
+		var len1 = knots1.length - degree1 - 1;
+		var len2 = knots2.length - degree2 - 1; // ensure THREE.Vector4 for control points
 
-	var len1 = knots1.length - degree1 - 1;
-	var len2 = knots2.length - degree2 - 1;
+		for ( var i = 0; i < len1; ++ i ) {
 
-	// ensure Vector4 for control points
-	for ( var i = 0; i < len1; ++ i ) {
+			this.controlPoints[ i ] = [];
 
-		this.controlPoints[ i ] = [];
-		for ( var j = 0; j < len2; ++ j ) {
+			for ( var j = 0; j < len2; ++ j ) {
 
-			var point = controlPoints[ i ][ j ];
-			this.controlPoints[ i ][ j ] = new THREE.Vector4( point.x, point.y, point.z, point.w );
+				var point = controlPoints[ i ][ j ];
+				this.controlPoints[ i ][ j ] = new THREE.Vector4( point.x, point.y, point.z, point.w );
 
-		}
+			}
 
-	}
+		}
 
-};
+	};
 
+	NURBSSurface.prototype = {
+		constructor: NURBSSurface,
+		getPoint: function ( t1, t2, target ) {
 
-THREE.NURBSSurface.prototype = {
+			var u = this.knots1[ 0 ] + t1 * ( this.knots1[ this.knots1.length - 1 ] - this.knots1[ 0 ] ); // linear mapping t1->u
 
-	constructor: THREE.NURBSSurface,
+			var v = this.knots2[ 0 ] + t2 * ( this.knots2[ this.knots2.length - 1 ] - this.knots2[ 0 ] ); // linear mapping t2->u
 
-	getPoint: function ( t1, t2, target ) {
+			THREE.NURBSUtils.calcSurfacePoint( this.degree1, this.degree2, this.knots1, this.knots2, this.controlPoints, u, v, target );
 
-		var u = this.knots1[ 0 ] + t1 * ( this.knots1[ this.knots1.length - 1 ] - this.knots1[ 0 ] ); // linear mapping t1->u
-		var v = this.knots2[ 0 ] + t2 * ( this.knots2[ this.knots2.length - 1 ] - this.knots2[ 0 ] ); // linear mapping t2->u
+		}
+	};
 
-		THREE.NURBSUtils.calcSurfacePoint( this.degree1, this.degree2, this.knots1, this.knots2, this.controlPoints, u, v, target );
+	THREE.NURBSSurface = NURBSSurface;
 
-	}
-};
+} )();

+ 258 - 285
examples/js/curves/NURBSUtils.js

@@ -1,470 +1,443 @@
-/**
+( function () {
+
+	/**
  * NURBS utils
  *
  * See NURBSCurve and NURBSSurface.
  **/
 
-
-/**************************************************************
+	/**************************************************************
  *	NURBS Utils
  **************************************************************/
 
-THREE.NURBSUtils = {
-
+	var NURBSUtils = {
 	/*
 	Finds knot vector span.
-
-	p : degree
+		p : degree
 	u : parametric value
 	U : knot vector
-
-	returns the span
+		returns the span
 	*/
-	findSpan: function ( p, u, U ) {
-
-		var n = U.length - p - 1;
+		findSpan: function ( p, u, U ) {
 
-		if ( u >= U[ n ] ) {
+			var n = U.length - p - 1;
 
-			return n - 1;
+			if ( u >= U[ n ] ) {
 
-		}
+				return n - 1;
 
-		if ( u <= U[ p ] ) {
+			}
 
-			return p;
+			if ( u <= U[ p ] ) {
 
-		}
+				return p;
 
-		var low = p;
-		var high = n;
-		var mid = Math.floor( ( low + high ) / 2 );
+			}
 
-		while ( u < U[ mid ] || u >= U[ mid + 1 ] ) {
+			var low = p;
+			var high = n;
+			var mid = Math.floor( ( low + high ) / 2 );
 
-			if ( u < U[ mid ] ) {
+			while ( u < U[ mid ] || u >= U[ mid + 1 ] ) {
 
-				high = mid;
+				if ( u < U[ mid ] ) {
 
-			} else {
+					high = mid;
 
-				low = mid;
+				} else {
 
-			}
+					low = mid;
 
-			mid = Math.floor( ( low + high ) / 2 );
+				}
 
-		}
+				mid = Math.floor( ( low + high ) / 2 );
 
-		return mid;
+			}
 
-	},
+			return mid;
 
+		},
 
-	/*
+		/*
 	Calculate basis functions. See The NURBS Book, page 70, algorithm A2.2
-
-	span : span in which u lies
-	u    : parametric point
-	p    : degree
-	U    : knot vector
-
-	returns array[p+1] with basis functions values.
+		span : span in which u lies
+	u		: parametric point
+	p		: degree
+	U		: knot vector
+		returns array[p+1] with basis functions values.
 	*/
-	calcBasisFunctions: function ( span, u, p, U ) {
-
-		var N = [];
-		var left = [];
-		var right = [];
-		N[ 0 ] = 1.0;
+		calcBasisFunctions: function ( span, u, p, U ) {
 
-		for ( var j = 1; j <= p; ++ j ) {
+			var N = [];
+			var left = [];
+			var right = [];
+			N[ 0 ] = 1.0;
 
-			left[ j ] = u - U[ span + 1 - j ];
-			right[ j ] = U[ span + j ] - u;
-
-			var saved = 0.0;
+			for ( var j = 1; j <= p; ++ j ) {
 
-			for ( var r = 0; r < j; ++ r ) {
+				left[ j ] = u - U[ span + 1 - j ];
+				right[ j ] = U[ span + j ] - u;
+				var saved = 0.0;
 
-				var rv = right[ r + 1 ];
-				var lv = left[ j - r ];
-				var temp = N[ r ] / ( rv + lv );
-				N[ r ] = saved + rv * temp;
-				saved = lv * temp;
+				for ( var r = 0; r < j; ++ r ) {
 
-			 }
+					var rv = right[ r + 1 ];
+					var lv = left[ j - r ];
+					var temp = N[ r ] / ( rv + lv );
+					N[ r ] = saved + rv * temp;
+					saved = lv * temp;
 
-			 N[ j ] = saved;
+				}
 
-		 }
+				N[ j ] = saved;
 
-		 return N;
+			}
 
-	},
+			return N;
 
+		},
 
-	/*
+		/*
 	Calculate B-Spline curve points. See The NURBS Book, page 82, algorithm A3.1.
-
-	p : degree of B-Spline
+		p : degree of B-Spline
 	U : knot vector
 	P : control points (x, y, z, w)
 	u : parametric point
-
-	returns point for given u
+		returns point for given u
 	*/
-	calcBSplinePoint: function ( p, U, P, u ) {
+		calcBSplinePoint: function ( p, U, P, u ) {
 
-		var span = this.findSpan( p, u, U );
-		var N = this.calcBasisFunctions( span, u, p, U );
-		var C = new THREE.Vector4( 0, 0, 0, 0 );
+			var span = this.findSpan( p, u, U );
+			var N = this.calcBasisFunctions( span, u, p, U );
+			var C = new THREE.Vector4( 0, 0, 0, 0 );
 
-		for ( var j = 0; j <= p; ++ j ) {
-
-			var point = P[ span - p + j ];
-			var Nj = N[ j ];
-			var wNj = point.w * Nj;
-			C.x += point.x * wNj;
-			C.y += point.y * wNj;
-			C.z += point.z * wNj;
-			C.w += point.w * Nj;
+			for ( var j = 0; j <= p; ++ j ) {
 
-		}
+				var point = P[ span - p + j ];
+				var Nj = N[ j ];
+				var wNj = point.w * Nj;
+				C.x += point.x * wNj;
+				C.y += point.y * wNj;
+				C.z += point.z * wNj;
+				C.w += point.w * Nj;
 
-		return C;
+			}
 
-	},
+			return C;
 
+		},
 
-	/*
+		/*
 	Calculate basis functions derivatives. See The NURBS Book, page 72, algorithm A2.3.
+		span : span in which u lies
+	u		: parametric point
+	p		: degree
+	n		: number of derivatives to calculate
+	U		: knot vector
+		returns array[n+1][p+1] with basis functions derivatives
+	*/
+		calcBasisFunctionDerivatives: function ( span, u, p, n, U ) {
 
-	span : span in which u lies
-	u    : parametric point
-	p    : degree
-	n    : number of derivatives to calculate
-	U    : knot vector
+			var zeroArr = [];
 
-	returns array[n+1][p+1] with basis functions derivatives
-	*/
-	calcBasisFunctionDerivatives: function ( span, u, p, n, U ) {
+			for ( var i = 0; i <= p; ++ i ) zeroArr[ i ] = 0.0;
 
-		var zeroArr = [];
-		for ( var i = 0; i <= p; ++ i )
-			zeroArr[ i ] = 0.0;
+			var ders = [];
 
-		var ders = [];
-		for ( var i = 0; i <= n; ++ i )
-			ders[ i ] = zeroArr.slice( 0 );
+			for ( var i = 0; i <= n; ++ i ) ders[ i ] = zeroArr.slice( 0 );
 
-		var ndu = [];
-		for ( var i = 0; i <= p; ++ i )
-			ndu[ i ] = zeroArr.slice( 0 );
+			var ndu = [];
 
-		ndu[ 0 ][ 0 ] = 1.0;
+			for ( var i = 0; i <= p; ++ i ) ndu[ i ] = zeroArr.slice( 0 );
 
-		var left = zeroArr.slice( 0 );
-		var right = zeroArr.slice( 0 );
+			ndu[ 0 ][ 0 ] = 1.0;
+			var left = zeroArr.slice( 0 );
+			var right = zeroArr.slice( 0 );
 
-		for ( var j = 1; j <= p; ++ j ) {
+			for ( var j = 1; j <= p; ++ j ) {
 
-			left[ j ] = u - U[ span + 1 - j ];
-			right[ j ] = U[ span + j ] - u;
+				left[ j ] = u - U[ span + 1 - j ];
+				right[ j ] = U[ span + j ] - u;
+				var saved = 0.0;
 
-			var saved = 0.0;
+				for ( var r = 0; r < j; ++ r ) {
 
-			for ( var r = 0; r < j; ++ r ) {
+					var rv = right[ r + 1 ];
+					var lv = left[ j - r ];
+					ndu[ j ][ r ] = rv + lv;
+					var temp = ndu[ r ][ j - 1 ] / ndu[ j ][ r ];
+					ndu[ r ][ j ] = saved + rv * temp;
+					saved = lv * temp;
 
-				var rv = right[ r + 1 ];
-				var lv = left[ j - r ];
-				ndu[ j ][ r ] = rv + lv;
+				}
 
-				var temp = ndu[ r ][ j - 1 ] / ndu[ j ][ r ];
-				ndu[ r ][ j ] = saved + rv * temp;
-				saved = lv * temp;
+				ndu[ j ][ j ] = saved;
 
 			}
 
-			ndu[ j ][ j ] = saved;
+			for ( var j = 0; j <= p; ++ j ) {
 
-		}
+				ders[ 0 ][ j ] = ndu[ j ][ p ];
 
-		for ( var j = 0; j <= p; ++ j ) {
+			}
 
-			ders[ 0 ][ j ] = ndu[ j ][ p ];
+			for ( var r = 0; r <= p; ++ r ) {
 
-		}
+				var s1 = 0;
+				var s2 = 1;
+				var a = [];
 
-		for ( var r = 0; r <= p; ++ r ) {
+				for ( var i = 0; i <= p; ++ i ) {
 
-			var s1 = 0;
-			var s2 = 1;
+					a[ i ] = zeroArr.slice( 0 );
 
-			var a = [];
-			for ( var i = 0; i <= p; ++ i ) {
+				}
 
-				a[ i ] = zeroArr.slice( 0 );
+				a[ 0 ][ 0 ] = 1.0;
 
-			}
+				for ( var k = 1; k <= n; ++ k ) {
 
-			a[ 0 ][ 0 ] = 1.0;
+					var d = 0.0;
+					var rk = r - k;
+					var pk = p - k;
 
-			for ( var k = 1; k <= n; ++ k ) {
+					if ( r >= k ) {
 
-				var d = 0.0;
-				var rk = r - k;
-				var pk = p - k;
+						a[ s2 ][ 0 ] = a[ s1 ][ 0 ] / ndu[ pk + 1 ][ rk ];
+						d = a[ s2 ][ 0 ] * ndu[ rk ][ pk ];
 
-				if ( r >= k ) {
+					}
 
-					a[ s2 ][ 0 ] = a[ s1 ][ 0 ] / ndu[ pk + 1 ][ rk ];
-					d = a[ s2 ][ 0 ] * ndu[ rk ][ pk ];
+					var j1 = rk >= - 1 ? 1 : - rk;
+					var j2 = r - 1 <= pk ? k - 1 : p - r;
 
-				}
+					for ( var j = j1; j <= j2; ++ j ) {
 
-				var j1 = ( rk >= - 1 ) ? 1 : - rk;
-				var j2 = ( r - 1 <= pk ) ? k - 1 : p - r;
+						a[ s2 ][ j ] = ( a[ s1 ][ j ] - a[ s1 ][ j - 1 ] ) / ndu[ pk + 1 ][ rk + j ];
+						d += a[ s2 ][ j ] * ndu[ rk + j ][ pk ];
 
-				for ( var j = j1; j <= j2; ++ j ) {
+					}
 
-					a[ s2 ][ j ] = ( a[ s1 ][ j ] - a[ s1 ][ j - 1 ] ) / ndu[ pk + 1 ][ rk + j ];
-					d += a[ s2 ][ j ] * ndu[ rk + j ][ pk ];
+					if ( r <= pk ) {
 
-				}
+						a[ s2 ][ k ] = - a[ s1 ][ k - 1 ] / ndu[ pk + 1 ][ r ];
+						d += a[ s2 ][ k ] * ndu[ r ][ pk ];
 
-				if ( r <= pk ) {
+					}
 
-					a[ s2 ][ k ] = - a[ s1 ][ k - 1 ] / ndu[ pk + 1 ][ r ];
-					d += a[ s2 ][ k ] * ndu[ r ][ pk ];
+					ders[ k ][ r ] = d;
+					var j = s1;
+					s1 = s2;
+					s2 = j;
 
 				}
 
-				ders[ k ][ r ] = d;
-
-				var j = s1;
-				s1 = s2;
-				s2 = j;
-
 			}
 
-		}
+			var r = p;
 
-		var r = p;
-
-		for ( var k = 1; k <= n; ++ k ) {
-
-			for ( var j = 0; j <= p; ++ j ) {
+			for ( var k = 1; k <= n; ++ k ) {
 
-				ders[ k ][ j ] *= r;
+				for ( var j = 0; j <= p; ++ j ) {
 
-			}
+					ders[ k ][ j ] *= r;
 
-			r *= p - k;
+				}
 
-		}
+				r *= p - k;
 
-		return ders;
+			}
 
-	},
+			return ders;
 
+		},
 
-	/*
+		/*
 		Calculate derivatives of a B-Spline. See The NURBS Book, page 93, algorithm A3.2.
-
-		p  : degree
-		U  : knot vector
-		P  : control points
-		u  : Parametric points
+			p	: degree
+		U	: knot vector
+		P	: control points
+		u	: Parametric points
 		nd : number of derivatives
-
-		returns array[d+1] with derivatives
+			returns array[d+1] with derivatives
 		*/
-	calcBSplineDerivatives: function ( p, U, P, u, nd ) {
+		calcBSplineDerivatives: function ( p, U, P, u, nd ) {
 
-		var du = nd < p ? nd : p;
-		var CK = [];
-		var span = this.findSpan( p, u, U );
-		var nders = this.calcBasisFunctionDerivatives( span, u, p, du, U );
-		var Pw = [];
+			var du = nd < p ? nd : p;
+			var CK = [];
+			var span = this.findSpan( p, u, U );
+			var nders = this.calcBasisFunctionDerivatives( span, u, p, du, U );
+			var Pw = [];
 
-		for ( var i = 0; i < P.length; ++ i ) {
+			for ( var i = 0; i < P.length; ++ i ) {
 
-			var point = P[ i ].clone();
-			var w = point.w;
+				var point = P[ i ].clone();
+				var w = point.w;
+				point.x *= w;
+				point.y *= w;
+				point.z *= w;
+				Pw[ i ] = point;
 
-			point.x *= w;
-			point.y *= w;
-			point.z *= w;
+			}
 
-			Pw[ i ] = point;
+			for ( var k = 0; k <= du; ++ k ) {
 
-		}
+				var point = Pw[ span - p ].clone().multiplyScalar( nders[ k ][ 0 ] );
 
-		for ( var k = 0; k <= du; ++ k ) {
+				for ( var j = 1; j <= p; ++ j ) {
 
-			var point = Pw[ span - p ].clone().multiplyScalar( nders[ k ][ 0 ] );
+					point.add( Pw[ span - p + j ].clone().multiplyScalar( nders[ k ][ j ] ) );
 
-			for ( var j = 1; j <= p; ++ j ) {
+				}
 
-				point.add( Pw[ span - p + j ].clone().multiplyScalar( nders[ k ][ j ] ) );
+				CK[ k ] = point;
 
 			}
 
-			CK[ k ] = point;
+			for ( var k = du + 1; k <= nd + 1; ++ k ) {
 
-		}
+				CK[ k ] = new THREE.Vector4( 0, 0, 0 );
 
-		for ( var k = du + 1; k <= nd + 1; ++ k ) {
-
-			CK[ k ] = new THREE.Vector4( 0, 0, 0 );
-
-		}
-
-		return CK;
+			}
 
-	},
+			return CK;
 
+		},
 
-	/*
+		/*
 	Calculate "K over I"
-
-	returns k!/(i!(k-i)!)
+		returns k!/(i!(k-i)!)
 	*/
-	calcKoverI: function ( k, i ) {
+		calcKoverI: function ( k, i ) {
 
-		var nom = 1;
+			var nom = 1;
 
-		for ( var j = 2; j <= k; ++ j ) {
+			for ( var j = 2; j <= k; ++ j ) {
 
-			nom *= j;
+				nom *= j;
 
-		}
+			}
 
-		var denom = 1;
+			var denom = 1;
 
-		for ( var j = 2; j <= i; ++ j ) {
+			for ( var j = 2; j <= i; ++ j ) {
 
-			denom *= j;
+				denom *= j;
 
-		}
-
-		for ( var j = 2; j <= k - i; ++ j ) {
+			}
 
-			denom *= j;
+			for ( var j = 2; j <= k - i; ++ j ) {
 
-		}
+				denom *= j;
 
-		return nom / denom;
+			}
 
-	},
+			return nom / denom;
 
+		},
 
-	/*
+		/*
 	Calculate derivatives (0-nd) of rational curve. See The NURBS Book, page 127, algorithm A4.2.
-
-	Pders : result of function calcBSplineDerivatives
-
-	returns array with derivatives for rational curve.
+		Pders : result of function calcBSplineDerivatives
+		returns array with derivatives for rational curve.
 	*/
-	calcRationalCurveDerivatives: function ( Pders ) {
+		calcRationalCurveDerivatives: function ( Pders ) {
 
-		var nd = Pders.length;
-		var Aders = [];
-		var wders = [];
+			var nd = Pders.length;
+			var Aders = [];
+			var wders = [];
 
-		for ( var i = 0; i < nd; ++ i ) {
+			for ( var i = 0; i < nd; ++ i ) {
 
-			var point = Pders[ i ];
-			Aders[ i ] = new THREE.Vector3( point.x, point.y, point.z );
-			wders[ i ] = point.w;
+				var point = Pders[ i ];
+				Aders[ i ] = new THREE.Vector3( point.x, point.y, point.z );
+				wders[ i ] = point.w;
 
-		}
+			}
 
-		var CK = [];
+			var CK = [];
 
-		for ( var k = 0; k < nd; ++ k ) {
+			for ( var k = 0; k < nd; ++ k ) {
 
-			var v = Aders[ k ].clone();
+				var v = Aders[ k ].clone();
 
-			for ( var i = 1; i <= k; ++ i ) {
+				for ( var i = 1; i <= k; ++ i ) {
 
-				v.sub( CK[ k - i ].clone().multiplyScalar( this.calcKoverI( k, i ) * wders[ i ] ) );
+					v.sub( CK[ k - i ].clone().multiplyScalar( this.calcKoverI( k, i ) * wders[ i ] ) );
 
-			}
-
-			CK[ k ] = v.divideScalar( wders[ 0 ] );
+				}
 
-		}
+				CK[ k ] = v.divideScalar( wders[ 0 ] );
 
-		return CK;
+			}
 
-	},
+			return CK;
 
+		},
 
-	/*
+		/*
 	Calculate NURBS curve derivatives. See The NURBS Book, page 127, algorithm A4.2.
-
-	p  : degree
-	U  : knot vector
-	P  : control points in homogeneous space
-	u  : parametric points
+		p	: degree
+	U	: knot vector
+	P	: control points in homogeneous space
+	u	: parametric points
 	nd : number of derivatives
-
-	returns array with derivatives.
+		returns array with derivatives.
 	*/
-	calcNURBSDerivatives: function ( p, U, P, u, nd ) {
+		calcNURBSDerivatives: function ( p, U, P, u, nd ) {
 
-		var Pders = this.calcBSplineDerivatives( p, U, P, u, nd );
-		return this.calcRationalCurveDerivatives( Pders );
+			var Pders = this.calcBSplineDerivatives( p, U, P, u, nd );
+			return this.calcRationalCurveDerivatives( Pders );
 
-	},
+		},
 
-
-	/*
+		/*
 	Calculate rational B-Spline surface point. See The NURBS Book, page 134, algorithm A4.3.
-
-	p1, p2 : degrees of B-Spline surface
+		p1, p2 : degrees of B-Spline surface
 	U1, U2 : knot vectors
-	P      : control points (x, y, z, w)
-	u, v   : parametric values
-
-	returns point for given (u, v)
+	P			: control points (x, y, z, w)
+	u, v	 : parametric values
+		returns point for given (u, v)
 	*/
-	calcSurfacePoint: function ( p, q, U, V, P, u, v, target ) {
+		calcSurfacePoint: function ( p, q, U, V, P, u, v, target ) {
 
-		var uspan = this.findSpan( p, u, U );
-		var vspan = this.findSpan( q, v, V );
-		var Nu = this.calcBasisFunctions( uspan, u, p, U );
-		var Nv = this.calcBasisFunctions( vspan, v, q, V );
-		var temp = [];
+			var uspan = this.findSpan( p, u, U );
+			var vspan = this.findSpan( q, v, V );
+			var Nu = this.calcBasisFunctions( uspan, u, p, U );
+			var Nv = this.calcBasisFunctions( vspan, v, q, V );
+			var temp = [];
 
-		for ( var l = 0; l <= q; ++ l ) {
+			for ( var l = 0; l <= q; ++ l ) {
 
-			temp[ l ] = new THREE.Vector4( 0, 0, 0, 0 );
-			for ( var k = 0; k <= p; ++ k ) {
+				temp[ l ] = new THREE.Vector4( 0, 0, 0, 0 );
 
-				var point = P[ uspan - p + k ][ vspan - q + l ].clone();
-				var w = point.w;
-				point.x *= w;
-				point.y *= w;
-				point.z *= w;
-				temp[ l ].add( point.multiplyScalar( Nu[ k ] ) );
+				for ( var k = 0; k <= p; ++ k ) {
+
+					var point = P[ uspan - p + k ][ vspan - q + l ].clone();
+					var w = point.w;
+					point.x *= w;
+					point.y *= w;
+					point.z *= w;
+					temp[ l ].add( point.multiplyScalar( Nu[ k ] ) );
+
+				}
 
 			}
 
-		}
+			var Sw = new THREE.Vector4( 0, 0, 0, 0 );
 
-		var Sw = new THREE.Vector4( 0, 0, 0, 0 );
-		for ( var l = 0; l <= q; ++ l ) {
+			for ( var l = 0; l <= q; ++ l ) {
 
-			Sw.add( temp[ l ].multiplyScalar( Nv[ l ] ) );
+				Sw.add( temp[ l ].multiplyScalar( Nv[ l ] ) );
 
-		}
+			}
+
+			Sw.divideScalar( Sw.w );
+			target.set( Sw.x, Sw.y, Sw.z );
 
-		Sw.divideScalar( Sw.w );
-		target.set( Sw.x, Sw.y, Sw.z );
+		}
+	};
 
-	}
+	THREE.NURBSUtils = NURBSUtils;
 
-};
+} )();

+ 1625 - 0
examples/js/deprecated/Geometry.js

@@ -0,0 +1,1625 @@
+( function () {
+
+	const _m1 = new THREE.Matrix4();
+
+	const _obj = new THREE.Object3D();
+
+	const _offset = new THREE.Vector3();
+
+	function Geometry() {
+
+		this.uuid = THREE.MathUtils.generateUUID();
+		this.name = '';
+		this.type = 'Geometry';
+		this.vertices = [];
+		this.colors = [];
+		this.faces = [];
+		this.faceVertexUvs = [[]];
+		this.morphTargets = [];
+		this.morphNormals = [];
+		this.skinWeights = [];
+		this.skinIndices = [];
+		this.lineDistances = [];
+		this.boundingBox = null;
+		this.boundingSphere = null; // update flags
+
+		this.elementsNeedUpdate = false;
+		this.verticesNeedUpdate = false;
+		this.uvsNeedUpdate = false;
+		this.normalsNeedUpdate = false;
+		this.colorsNeedUpdate = false;
+		this.lineDistancesNeedUpdate = false;
+		this.groupsNeedUpdate = false;
+
+	}
+
+	Geometry.prototype = Object.assign( Object.create( THREE.EventDispatcher.prototype ), {
+		constructor: Geometry,
+		isGeometry: true,
+		applyMatrix4: function ( matrix ) {
+
+			const normalMatrix = new THREE.Matrix3().getNormalMatrix( matrix );
+
+			for ( let i = 0, il = this.vertices.length; i < il; i ++ ) {
+
+				const vertex = this.vertices[ i ];
+				vertex.applyMatrix4( matrix );
+
+			}
+
+			for ( let i = 0, il = this.faces.length; i < il; i ++ ) {
+
+				const face = this.faces[ i ];
+				face.normal.applyMatrix3( normalMatrix ).normalize();
+
+				for ( let j = 0, jl = face.vertexNormals.length; j < jl; j ++ ) {
+
+					face.vertexNormals[ j ].applyMatrix3( normalMatrix ).normalize();
+
+				}
+
+			}
+
+			if ( this.boundingBox !== null ) {
+
+				this.computeBoundingBox();
+
+			}
+
+			if ( this.boundingSphere !== null ) {
+
+				this.computeBoundingSphere();
+
+			}
+
+			this.verticesNeedUpdate = true;
+			this.normalsNeedUpdate = true;
+			return this;
+
+		},
+		rotateX: function ( angle ) {
+
+			// rotate geometry around world x-axis
+			_m1.makeRotationX( angle );
+
+			this.applyMatrix4( _m1 );
+			return this;
+
+		},
+		rotateY: function ( angle ) {
+
+			// rotate geometry around world y-axis
+			_m1.makeRotationY( angle );
+
+			this.applyMatrix4( _m1 );
+			return this;
+
+		},
+		rotateZ: function ( angle ) {
+
+			// rotate geometry around world z-axis
+			_m1.makeRotationZ( angle );
+
+			this.applyMatrix4( _m1 );
+			return this;
+
+		},
+		translate: function ( x, y, z ) {
+
+			// translate geometry
+			_m1.makeTranslation( x, y, z );
+
+			this.applyMatrix4( _m1 );
+			return this;
+
+		},
+		scale: function ( x, y, z ) {
+
+			// scale geometry
+			_m1.makeScale( x, y, z );
+
+			this.applyMatrix4( _m1 );
+			return this;
+
+		},
+		lookAt: function ( vector ) {
+
+			_obj.lookAt( vector );
+
+			_obj.updateMatrix();
+
+			this.applyMatrix4( _obj.matrix );
+			return this;
+
+		},
+		fromBufferGeometry: function ( geometry ) {
+
+			const scope = this;
+			const index = geometry.index !== null ? geometry.index : undefined;
+			const attributes = geometry.attributes;
+
+			if ( attributes.position === undefined ) {
+
+				console.error( 'THREE.Geometry.fromBufferGeometry(): Position attribute required for conversion.' );
+				return this;
+
+			}
+
+			const position = attributes.position;
+			const normal = attributes.normal;
+			const color = attributes.color;
+			const uv = attributes.uv;
+			const uv2 = attributes.uv2;
+			if ( uv2 !== undefined ) this.faceVertexUvs[ 1 ] = [];
+
+			for ( let i = 0; i < position.count; i ++ ) {
+
+				scope.vertices.push( new THREE.Vector3().fromBufferAttribute( position, i ) );
+
+				if ( color !== undefined ) {
+
+					scope.colors.push( new THREE.Color().fromBufferAttribute( color, i ) );
+
+				}
+
+			}
+
+			function addFace( a, b, c, materialIndex ) {
+
+				const vertexColors = color === undefined ? [] : [ scope.colors[ a ].clone(), scope.colors[ b ].clone(), scope.colors[ c ].clone() ];
+				const vertexNormals = normal === undefined ? [] : [ new THREE.Vector3().fromBufferAttribute( normal, a ), new THREE.Vector3().fromBufferAttribute( normal, b ), new THREE.Vector3().fromBufferAttribute( normal, c ) ];
+				const face = new Face3( a, b, c, vertexNormals, vertexColors, materialIndex );
+				scope.faces.push( face );
+
+				if ( uv !== undefined ) {
+
+					scope.faceVertexUvs[ 0 ].push( [ new THREE.Vector2().fromBufferAttribute( uv, a ), new THREE.Vector2().fromBufferAttribute( uv, b ), new THREE.Vector2().fromBufferAttribute( uv, c ) ] );
+
+				}
+
+				if ( uv2 !== undefined ) {
+
+					scope.faceVertexUvs[ 1 ].push( [ new THREE.Vector2().fromBufferAttribute( uv2, a ), new THREE.Vector2().fromBufferAttribute( uv2, b ), new THREE.Vector2().fromBufferAttribute( uv2, c ) ] );
+
+				}
+
+			}
+
+			const groups = geometry.groups;
+
+			if ( groups.length > 0 ) {
+
+				for ( let i = 0; i < groups.length; i ++ ) {
+
+					const group = groups[ i ];
+					const start = group.start;
+					const count = group.count;
+
+					for ( let j = start, jl = start + count; j < jl; j += 3 ) {
+
+						if ( index !== undefined ) {
+
+							addFace( index.getX( j ), index.getX( j + 1 ), index.getX( j + 2 ), group.materialIndex );
+
+						} else {
+
+							addFace( j, j + 1, j + 2, group.materialIndex );
+
+						}
+
+					}
+
+				}
+
+			} else {
+
+				if ( index !== undefined ) {
+
+					for ( let i = 0; i < index.count; i += 3 ) {
+
+						addFace( index.getX( i ), index.getX( i + 1 ), index.getX( i + 2 ) );
+
+					}
+
+				} else {
+
+					for ( let i = 0; i < position.count; i += 3 ) {
+
+						addFace( i, i + 1, i + 2 );
+
+					}
+
+				}
+
+			}
+
+			this.computeFaceNormals();
+
+			if ( geometry.boundingBox !== null ) {
+
+				this.boundingBox = geometry.boundingBox.clone();
+
+			}
+
+			if ( geometry.boundingSphere !== null ) {
+
+				this.boundingSphere = geometry.boundingSphere.clone();
+
+			}
+
+			return this;
+
+		},
+		center: function () {
+
+			this.computeBoundingBox();
+			this.boundingBox.getCenter( _offset ).negate();
+			this.translate( _offset.x, _offset.y, _offset.z );
+			return this;
+
+		},
+		normalize: function () {
+
+			this.computeBoundingSphere();
+			const center = this.boundingSphere.center;
+			const radius = this.boundingSphere.radius;
+			const s = radius === 0 ? 1 : 1.0 / radius;
+			const matrix = new THREE.Matrix4();
+			matrix.set( s, 0, 0, - s * center.x, 0, s, 0, - s * center.y, 0, 0, s, - s * center.z, 0, 0, 0, 1 );
+			this.applyMatrix4( matrix );
+			return this;
+
+		},
+		computeFaceNormals: function () {
+
+			const cb = new THREE.Vector3(),
+				ab = new THREE.Vector3();
+
+			for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+				const face = this.faces[ f ];
+				const vA = this.vertices[ face.a ];
+				const vB = this.vertices[ face.b ];
+				const vC = this.vertices[ face.c ];
+				cb.subVectors( vC, vB );
+				ab.subVectors( vA, vB );
+				cb.cross( ab );
+				cb.normalize();
+				face.normal.copy( cb );
+
+			}
+
+		},
+		computeVertexNormals: function ( areaWeighted = true ) {
+
+			const vertices = new Array( this.vertices.length );
+
+			for ( let v = 0, vl = this.vertices.length; v < vl; v ++ ) {
+
+				vertices[ v ] = new THREE.Vector3();
+
+			}
+
+			if ( areaWeighted ) {
+
+				// vertex normals weighted by triangle areas
+				// http://www.iquilezles.org/www/articles/normals/normals.htm
+				const cb = new THREE.Vector3(),
+					ab = new THREE.Vector3();
+
+				for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+					const face = this.faces[ f ];
+					const vA = this.vertices[ face.a ];
+					const vB = this.vertices[ face.b ];
+					const vC = this.vertices[ face.c ];
+					cb.subVectors( vC, vB );
+					ab.subVectors( vA, vB );
+					cb.cross( ab );
+					vertices[ face.a ].add( cb );
+					vertices[ face.b ].add( cb );
+					vertices[ face.c ].add( cb );
+
+				}
+
+			} else {
+
+				this.computeFaceNormals();
+
+				for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+					const face = this.faces[ f ];
+					vertices[ face.a ].add( face.normal );
+					vertices[ face.b ].add( face.normal );
+					vertices[ face.c ].add( face.normal );
+
+				}
+
+			}
+
+			for ( let v = 0, vl = this.vertices.length; v < vl; v ++ ) {
+
+				vertices[ v ].normalize();
+
+			}
+
+			for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+				const face = this.faces[ f ];
+				const vertexNormals = face.vertexNormals;
+
+				if ( vertexNormals.length === 3 ) {
+
+					vertexNormals[ 0 ].copy( vertices[ face.a ] );
+					vertexNormals[ 1 ].copy( vertices[ face.b ] );
+					vertexNormals[ 2 ].copy( vertices[ face.c ] );
+
+				} else {
+
+					vertexNormals[ 0 ] = vertices[ face.a ].clone();
+					vertexNormals[ 1 ] = vertices[ face.b ].clone();
+					vertexNormals[ 2 ] = vertices[ face.c ].clone();
+
+				}
+
+			}
+
+			if ( this.faces.length > 0 ) {
+
+				this.normalsNeedUpdate = true;
+
+			}
+
+		},
+		computeFlatVertexNormals: function () {
+
+			this.computeFaceNormals();
+
+			for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+				const face = this.faces[ f ];
+				const vertexNormals = face.vertexNormals;
+
+				if ( vertexNormals.length === 3 ) {
+
+					vertexNormals[ 0 ].copy( face.normal );
+					vertexNormals[ 1 ].copy( face.normal );
+					vertexNormals[ 2 ].copy( face.normal );
+
+				} else {
+
+					vertexNormals[ 0 ] = face.normal.clone();
+					vertexNormals[ 1 ] = face.normal.clone();
+					vertexNormals[ 2 ] = face.normal.clone();
+
+				}
+
+			}
+
+			if ( this.faces.length > 0 ) {
+
+				this.normalsNeedUpdate = true;
+
+			}
+
+		},
+		computeMorphNormals: function () {
+
+			// save original normals
+			// - create temp variables on first access
+			//	 otherwise just copy (for faster repeated calls)
+			for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+				const face = this.faces[ f ];
+
+				if ( ! face.__originalFaceNormal ) {
+
+					face.__originalFaceNormal = face.normal.clone();
+
+				} else {
+
+					face.__originalFaceNormal.copy( face.normal );
+
+				}
+
+				if ( ! face.__originalVertexNormals ) face.__originalVertexNormals = [];
+
+				for ( let i = 0, il = face.vertexNormals.length; i < il; i ++ ) {
+
+					if ( ! face.__originalVertexNormals[ i ] ) {
+
+						face.__originalVertexNormals[ i ] = face.vertexNormals[ i ].clone();
+
+					} else {
+
+						face.__originalVertexNormals[ i ].copy( face.vertexNormals[ i ] );
+
+					}
+
+				}
+
+			} // use temp geometry to compute face and vertex normals for each morph
+
+
+			const tmpGeo = new Geometry();
+			tmpGeo.faces = this.faces;
+
+			for ( let i = 0, il = this.morphTargets.length; i < il; i ++ ) {
+
+				// create on first access
+				if ( ! this.morphNormals[ i ] ) {
+
+					this.morphNormals[ i ] = {};
+					this.morphNormals[ i ].faceNormals = [];
+					this.morphNormals[ i ].vertexNormals = [];
+					const dstNormalsFace = this.morphNormals[ i ].faceNormals;
+					const dstNormalsVertex = this.morphNormals[ i ].vertexNormals;
+
+					for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+						const faceNormal = new THREE.Vector3();
+						const vertexNormals = {
+							a: new THREE.Vector3(),
+							b: new THREE.Vector3(),
+							c: new THREE.Vector3()
+						};
+						dstNormalsFace.push( faceNormal );
+						dstNormalsVertex.push( vertexNormals );
+
+					}
+
+				}
+
+				const morphNormals = this.morphNormals[ i ]; // set vertices to morph target
+
+				tmpGeo.vertices = this.morphTargets[ i ].vertices; // compute morph normals
+
+				tmpGeo.computeFaceNormals();
+				tmpGeo.computeVertexNormals(); // store morph normals
+
+				for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+					const face = this.faces[ f ];
+					const faceNormal = morphNormals.faceNormals[ f ];
+					const vertexNormals = morphNormals.vertexNormals[ f ];
+					faceNormal.copy( face.normal );
+					vertexNormals.a.copy( face.vertexNormals[ 0 ] );
+					vertexNormals.b.copy( face.vertexNormals[ 1 ] );
+					vertexNormals.c.copy( face.vertexNormals[ 2 ] );
+
+				}
+
+			} // restore original normals
+
+
+			for ( let f = 0, fl = this.faces.length; f < fl; f ++ ) {
+
+				const face = this.faces[ f ];
+				face.normal = face.__originalFaceNormal;
+				face.vertexNormals = face.__originalVertexNormals;
+
+			}
+
+		},
+		computeBoundingBox: function () {
+
+			if ( this.boundingBox === null ) {
+
+				this.boundingBox = new THREE.Box3();
+
+			}
+
+			this.boundingBox.setFromPoints( this.vertices );
+
+		},
+		computeBoundingSphere: function () {
+
+			if ( this.boundingSphere === null ) {
+
+				this.boundingSphere = new THREE.Sphere();
+
+			}
+
+			this.boundingSphere.setFromPoints( this.vertices );
+
+		},
+		merge: function ( geometry, matrix, materialIndexOffset = 0 ) {
+
+			if ( ! ( geometry && geometry.isGeometry ) ) {
+
+				console.error( 'THREE.Geometry.merge(): geometry not an instance of THREE.Geometry.', geometry );
+				return;
+
+			}
+
+			let normalMatrix;
+			const vertexOffset = this.vertices.length,
+				vertices1 = this.vertices,
+				vertices2 = geometry.vertices,
+				faces1 = this.faces,
+				faces2 = geometry.faces,
+				colors1 = this.colors,
+				colors2 = geometry.colors;
+
+			if ( matrix !== undefined ) {
+
+				normalMatrix = new THREE.Matrix3().getNormalMatrix( matrix );
+
+			} // vertices
+
+
+			for ( let i = 0, il = vertices2.length; i < il; i ++ ) {
+
+				const vertex = vertices2[ i ];
+				const vertexCopy = vertex.clone();
+				if ( matrix !== undefined ) vertexCopy.applyMatrix4( matrix );
+				vertices1.push( vertexCopy );
+
+			} // colors
+
+
+			for ( let i = 0, il = colors2.length; i < il; i ++ ) {
+
+				colors1.push( colors2[ i ].clone() );
+
+			} // faces
+
+
+			for ( let i = 0, il = faces2.length; i < il; i ++ ) {
+
+				const face = faces2[ i ];
+				let normal, color;
+				const faceVertexNormals = face.vertexNormals,
+					faceVertexColors = face.vertexColors;
+				const faceCopy = new Face3( face.a + vertexOffset, face.b + vertexOffset, face.c + vertexOffset );
+				faceCopy.normal.copy( face.normal );
+
+				if ( normalMatrix !== undefined ) {
+
+					faceCopy.normal.applyMatrix3( normalMatrix ).normalize();
+
+				}
+
+				for ( let j = 0, jl = faceVertexNormals.length; j < jl; j ++ ) {
+
+					normal = faceVertexNormals[ j ].clone();
+
+					if ( normalMatrix !== undefined ) {
+
+						normal.applyMatrix3( normalMatrix ).normalize();
+
+					}
+
+					faceCopy.vertexNormals.push( normal );
+
+				}
+
+				faceCopy.color.copy( face.color );
+
+				for ( let j = 0, jl = faceVertexColors.length; j < jl; j ++ ) {
+
+					color = faceVertexColors[ j ];
+					faceCopy.vertexColors.push( color.clone() );
+
+				}
+
+				faceCopy.materialIndex = face.materialIndex + materialIndexOffset;
+				faces1.push( faceCopy );
+
+			} // uvs
+
+
+			for ( let i = 0, il = geometry.faceVertexUvs.length; i < il; i ++ ) {
+
+				const faceVertexUvs2 = geometry.faceVertexUvs[ i ];
+				if ( this.faceVertexUvs[ i ] === undefined ) this.faceVertexUvs[ i ] = [];
+
+				for ( let j = 0, jl = faceVertexUvs2.length; j < jl; j ++ ) {
+
+					const uvs2 = faceVertexUvs2[ j ],
+						uvsCopy = [];
+
+					for ( let k = 0, kl = uvs2.length; k < kl; k ++ ) {
+
+						uvsCopy.push( uvs2[ k ].clone() );
+
+					}
+
+					this.faceVertexUvs[ i ].push( uvsCopy );
+
+				}
+
+			}
+
+		},
+		mergeMesh: function ( mesh ) {
+
+			if ( ! ( mesh && mesh.isMesh ) ) {
+
+				console.error( 'THREE.Geometry.mergeMesh(): mesh not an instance of THREE.Mesh.', mesh );
+				return;
+
+			}
+
+			if ( mesh.matrixAutoUpdate ) mesh.updateMatrix();
+			this.merge( mesh.geometry, mesh.matrix );
+
+		},
+
+		/*
+	 * Checks for duplicate vertices with hashmap.
+	 * Duplicated vertices are removed
+	 * and faces' vertices are updated.
+	 */
+		mergeVertices: function ( precisionPoints = 4 ) {
+
+			const verticesMap = {}; // Hashmap for looking up vertices by position coordinates (and making sure they are unique)
+
+			const unique = [],
+				changes = [];
+			const precision = Math.pow( 10, precisionPoints );
+
+			for ( let i = 0, il = this.vertices.length; i < il; i ++ ) {
+
+				const v = this.vertices[ i ];
+				const key = Math.round( v.x * precision ) + '_' + Math.round( v.y * precision ) + '_' + Math.round( v.z * precision );
+
+				if ( verticesMap[ key ] === undefined ) {
+
+					verticesMap[ key ] = i;
+					unique.push( this.vertices[ i ] );
+					changes[ i ] = unique.length - 1;
+
+				} else {
+
+					//console.log('Duplicate vertex found. ', i, ' could be using ', verticesMap[key]);
+					changes[ i ] = changes[ verticesMap[ key ] ];
+
+				}
+
+			} // if faces are completely degenerate after merging vertices, we
+			// have to remove them from the geometry.
+
+
+			const faceIndicesToRemove = [];
+
+			for ( let i = 0, il = this.faces.length; i < il; i ++ ) {
+
+				const face = this.faces[ i ];
+				face.a = changes[ face.a ];
+				face.b = changes[ face.b ];
+				face.c = changes[ face.c ];
+				const indices = [ face.a, face.b, face.c ]; // if any duplicate vertices are found in a Face3
+				// we have to remove the face as nothing can be saved
+
+				for ( let n = 0; n < 3; n ++ ) {
+
+					if ( indices[ n ] === indices[ ( n + 1 ) % 3 ] ) {
+
+						faceIndicesToRemove.push( i );
+						break;
+
+					}
+
+				}
+
+			}
+
+			for ( let i = faceIndicesToRemove.length - 1; i >= 0; i -- ) {
+
+				const idx = faceIndicesToRemove[ i ];
+				this.faces.splice( idx, 1 );
+
+				for ( let j = 0, jl = this.faceVertexUvs.length; j < jl; j ++ ) {
+
+					this.faceVertexUvs[ j ].splice( idx, 1 );
+
+				}
+
+			} // Use unique set of vertices
+
+
+			const diff = this.vertices.length - unique.length;
+			this.vertices = unique;
+			return diff;
+
+		},
+		setFromPoints: function ( points ) {
+
+			this.vertices = [];
+
+			for ( let i = 0, l = points.length; i < l; i ++ ) {
+
+				const point = points[ i ];
+				this.vertices.push( new THREE.Vector3( point.x, point.y, point.z || 0 ) );
+
+			}
+
+			return this;
+
+		},
+		sortFacesByMaterialIndex: function () {
+
+			const faces = this.faces;
+			const length = faces.length; // tag faces
+
+			for ( let i = 0; i < length; i ++ ) {
+
+				faces[ i ]._id = i;
+
+			} // sort faces
+
+
+			function materialIndexSort( a, b ) {
+
+				return a.materialIndex - b.materialIndex;
+
+			}
+
+			faces.sort( materialIndexSort ); // sort uvs
+
+			const uvs1 = this.faceVertexUvs[ 0 ];
+			const uvs2 = this.faceVertexUvs[ 1 ];
+			let newUvs1, newUvs2;
+			if ( uvs1 && uvs1.length === length ) newUvs1 = [];
+			if ( uvs2 && uvs2.length === length ) newUvs2 = [];
+
+			for ( let i = 0; i < length; i ++ ) {
+
+				const id = faces[ i ]._id;
+				if ( newUvs1 ) newUvs1.push( uvs1[ id ] );
+				if ( newUvs2 ) newUvs2.push( uvs2[ id ] );
+
+			}
+
+			if ( newUvs1 ) this.faceVertexUvs[ 0 ] = newUvs1;
+			if ( newUvs2 ) this.faceVertexUvs[ 1 ] = newUvs2;
+
+		},
+		toJSON: function () {
+
+			const data = {
+				metadata: {
+					version: 4.5,
+					type: 'Geometry',
+					generator: 'Geometry.toJSON'
+				}
+			}; // standard Geometry serialization
+
+			data.uuid = this.uuid;
+			data.type = this.type;
+			if ( this.name !== '' ) data.name = this.name;
+
+			if ( this.parameters !== undefined ) {
+
+				const parameters = this.parameters;
+
+				for ( const key in parameters ) {
+
+					if ( parameters[ key ] !== undefined ) data[ key ] = parameters[ key ];
+
+				}
+
+				return data;
+
+			}
+
+			const vertices = [];
+
+			for ( let i = 0; i < this.vertices.length; i ++ ) {
+
+				const vertex = this.vertices[ i ];
+				vertices.push( vertex.x, vertex.y, vertex.z );
+
+			}
+
+			const faces = [];
+			const normals = [];
+			const normalsHash = {};
+			const colors = [];
+			const colorsHash = {};
+			const uvs = [];
+			const uvsHash = {};
+
+			for ( let i = 0; i < this.faces.length; i ++ ) {
+
+				const face = this.faces[ i ];
+				const hasMaterial = true;
+				const hasFaceUv = false; // deprecated
+
+				const hasFaceVertexUv = this.faceVertexUvs[ 0 ][ i ] !== undefined;
+				const hasFaceNormal = face.normal.length() > 0;
+				const hasFaceVertexNormal = face.vertexNormals.length > 0;
+				const hasFaceColor = face.color.r !== 1 || face.color.g !== 1 || face.color.b !== 1;
+				const hasFaceVertexColor = face.vertexColors.length > 0;
+				let faceType = 0;
+				faceType = setBit( faceType, 0, 0 ); // isQuad
+
+				faceType = setBit( faceType, 1, hasMaterial );
+				faceType = setBit( faceType, 2, hasFaceUv );
+				faceType = setBit( faceType, 3, hasFaceVertexUv );
+				faceType = setBit( faceType, 4, hasFaceNormal );
+				faceType = setBit( faceType, 5, hasFaceVertexNormal );
+				faceType = setBit( faceType, 6, hasFaceColor );
+				faceType = setBit( faceType, 7, hasFaceVertexColor );
+				faces.push( faceType );
+				faces.push( face.a, face.b, face.c );
+				faces.push( face.materialIndex );
+
+				if ( hasFaceVertexUv ) {
+
+					const faceVertexUvs = this.faceVertexUvs[ 0 ][ i ];
+					faces.push( getUvIndex( faceVertexUvs[ 0 ] ), getUvIndex( faceVertexUvs[ 1 ] ), getUvIndex( faceVertexUvs[ 2 ] ) );
+
+				}
+
+				if ( hasFaceNormal ) {
+
+					faces.push( getNormalIndex( face.normal ) );
+
+				}
+
+				if ( hasFaceVertexNormal ) {
+
+					const vertexNormals = face.vertexNormals;
+					faces.push( getNormalIndex( vertexNormals[ 0 ] ), getNormalIndex( vertexNormals[ 1 ] ), getNormalIndex( vertexNormals[ 2 ] ) );
+
+				}
+
+				if ( hasFaceColor ) {
+
+					faces.push( getColorIndex( face.color ) );
+
+				}
+
+				if ( hasFaceVertexColor ) {
+
+					const vertexColors = face.vertexColors;
+					faces.push( getColorIndex( vertexColors[ 0 ] ), getColorIndex( vertexColors[ 1 ] ), getColorIndex( vertexColors[ 2 ] ) );
+
+				}
+
+			}
+
+			function setBit( value, position, enabled ) {
+
+				return enabled ? value | 1 << position : value & ~ ( 1 << position );
+
+			}
+
+			function getNormalIndex( normal ) {
+
+				const hash = normal.x.toString() + normal.y.toString() + normal.z.toString();
+
+				if ( normalsHash[ hash ] !== undefined ) {
+
+					return normalsHash[ hash ];
+
+				}
+
+				normalsHash[ hash ] = normals.length / 3;
+				normals.push( normal.x, normal.y, normal.z );
+				return normalsHash[ hash ];
+
+			}
+
+			function getColorIndex( color ) {
+
+				const hash = color.r.toString() + color.g.toString() + color.b.toString();
+
+				if ( colorsHash[ hash ] !== undefined ) {
+
+					return colorsHash[ hash ];
+
+				}
+
+				colorsHash[ hash ] = colors.length;
+				colors.push( color.getHex() );
+				return colorsHash[ hash ];
+
+			}
+
+			function getUvIndex( uv ) {
+
+				const hash = uv.x.toString() + uv.y.toString();
+
+				if ( uvsHash[ hash ] !== undefined ) {
+
+					return uvsHash[ hash ];
+
+				}
+
+				uvsHash[ hash ] = uvs.length / 2;
+				uvs.push( uv.x, uv.y );
+				return uvsHash[ hash ];
+
+			}
+
+			data.data = {};
+			data.data.vertices = vertices;
+			data.data.normals = normals;
+			if ( colors.length > 0 ) data.data.colors = colors;
+			if ( uvs.length > 0 ) data.data.uvs = [ uvs ]; // temporal backward compatibility
+
+			data.data.faces = faces;
+			return data;
+
+		},
+		clone: function () {
+
+			/*
+		 // Handle primitives
+			 const parameters = this.parameters;
+			 if ( parameters !== undefined ) {
+			 const values = [];
+			 for ( const key in parameters ) {
+			 values.push( parameters[ key ] );
+			 }
+			 const geometry = Object.create( this.constructor.prototype );
+		 this.constructor.apply( geometry, values );
+		 return geometry;
+			 }
+			 return new this.constructor().copy( this );
+		 */
+			return new Geometry().copy( this );
+
+		},
+		copy: function ( source ) {
+
+			// reset
+			this.vertices = [];
+			this.colors = [];
+			this.faces = [];
+			this.faceVertexUvs = [[]];
+			this.morphTargets = [];
+			this.morphNormals = [];
+			this.skinWeights = [];
+			this.skinIndices = [];
+			this.lineDistances = [];
+			this.boundingBox = null;
+			this.boundingSphere = null; // name
+
+			this.name = source.name; // vertices
+
+			const vertices = source.vertices;
+
+			for ( let i = 0, il = vertices.length; i < il; i ++ ) {
+
+				this.vertices.push( vertices[ i ].clone() );
+
+			} // colors
+
+
+			const colors = source.colors;
+
+			for ( let i = 0, il = colors.length; i < il; i ++ ) {
+
+				this.colors.push( colors[ i ].clone() );
+
+			} // faces
+
+
+			const faces = source.faces;
+
+			for ( let i = 0, il = faces.length; i < il; i ++ ) {
+
+				this.faces.push( faces[ i ].clone() );
+
+			} // face vertex uvs
+
+
+			for ( let i = 0, il = source.faceVertexUvs.length; i < il; i ++ ) {
+
+				const faceVertexUvs = source.faceVertexUvs[ i ];
+
+				if ( this.faceVertexUvs[ i ] === undefined ) {
+
+					this.faceVertexUvs[ i ] = [];
+
+				}
+
+				for ( let j = 0, jl = faceVertexUvs.length; j < jl; j ++ ) {
+
+					const uvs = faceVertexUvs[ j ],
+						uvsCopy = [];
+
+					for ( let k = 0, kl = uvs.length; k < kl; k ++ ) {
+
+						const uv = uvs[ k ];
+						uvsCopy.push( uv.clone() );
+
+					}
+
+					this.faceVertexUvs[ i ].push( uvsCopy );
+
+				}
+
+			} // morph targets
+
+
+			const morphTargets = source.morphTargets;
+
+			for ( let i = 0, il = morphTargets.length; i < il; i ++ ) {
+
+				const morphTarget = {};
+				morphTarget.name = morphTargets[ i ].name; // vertices
+
+				if ( morphTargets[ i ].vertices !== undefined ) {
+
+					morphTarget.vertices = [];
+
+					for ( let j = 0, jl = morphTargets[ i ].vertices.length; j < jl; j ++ ) {
+
+						morphTarget.vertices.push( morphTargets[ i ].vertices[ j ].clone() );
+
+					}
+
+				} // normals
+
+
+				if ( morphTargets[ i ].normals !== undefined ) {
+
+					morphTarget.normals = [];
+
+					for ( let j = 0, jl = morphTargets[ i ].normals.length; j < jl; j ++ ) {
+
+						morphTarget.normals.push( morphTargets[ i ].normals[ j ].clone() );
+
+					}
+
+				}
+
+				this.morphTargets.push( morphTarget );
+
+			} // morph normals
+
+
+			const morphNormals = source.morphNormals;
+
+			for ( let i = 0, il = morphNormals.length; i < il; i ++ ) {
+
+				const morphNormal = {}; // vertex normals
+
+				if ( morphNormals[ i ].vertexNormals !== undefined ) {
+
+					morphNormal.vertexNormals = [];
+
+					for ( let j = 0, jl = morphNormals[ i ].vertexNormals.length; j < jl; j ++ ) {
+
+						const srcVertexNormal = morphNormals[ i ].vertexNormals[ j ];
+						const destVertexNormal = {};
+						destVertexNormal.a = srcVertexNormal.a.clone();
+						destVertexNormal.b = srcVertexNormal.b.clone();
+						destVertexNormal.c = srcVertexNormal.c.clone();
+						morphNormal.vertexNormals.push( destVertexNormal );
+
+					}
+
+				} // face normals
+
+
+				if ( morphNormals[ i ].faceNormals !== undefined ) {
+
+					morphNormal.faceNormals = [];
+
+					for ( let j = 0, jl = morphNormals[ i ].faceNormals.length; j < jl; j ++ ) {
+
+						morphNormal.faceNormals.push( morphNormals[ i ].faceNormals[ j ].clone() );
+
+					}
+
+				}
+
+				this.morphNormals.push( morphNormal );
+
+			} // skin weights
+
+
+			const skinWeights = source.skinWeights;
+
+			for ( let i = 0, il = skinWeights.length; i < il; i ++ ) {
+
+				this.skinWeights.push( skinWeights[ i ].clone() );
+
+			} // skin indices
+
+
+			const skinIndices = source.skinIndices;
+
+			for ( let i = 0, il = skinIndices.length; i < il; i ++ ) {
+
+				this.skinIndices.push( skinIndices[ i ].clone() );
+
+			} // line distances
+
+
+			const lineDistances = source.lineDistances;
+
+			for ( let i = 0, il = lineDistances.length; i < il; i ++ ) {
+
+				this.lineDistances.push( lineDistances[ i ] );
+
+			} // bounding box
+
+
+			const boundingBox = source.boundingBox;
+
+			if ( boundingBox !== null ) {
+
+				this.boundingBox = boundingBox.clone();
+
+			} // bounding sphere
+
+
+			const boundingSphere = source.boundingSphere;
+
+			if ( boundingSphere !== null ) {
+
+				this.boundingSphere = boundingSphere.clone();
+
+			} // update flags
+
+
+			this.elementsNeedUpdate = source.elementsNeedUpdate;
+			this.verticesNeedUpdate = source.verticesNeedUpdate;
+			this.uvsNeedUpdate = source.uvsNeedUpdate;
+			this.normalsNeedUpdate = source.normalsNeedUpdate;
+			this.colorsNeedUpdate = source.colorsNeedUpdate;
+			this.lineDistancesNeedUpdate = source.lineDistancesNeedUpdate;
+			this.groupsNeedUpdate = source.groupsNeedUpdate;
+			return this;
+
+		},
+		toBufferGeometry: function () {
+
+			const geometry = new DirectGeometry().fromGeometry( this );
+			const buffergeometry = new THREE.BufferGeometry();
+			const positions = new Float32Array( geometry.vertices.length * 3 );
+			buffergeometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ).copyVector3sArray( geometry.vertices ) );
+
+			if ( geometry.normals.length > 0 ) {
+
+				const normals = new Float32Array( geometry.normals.length * 3 );
+				buffergeometry.setAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ).copyVector3sArray( geometry.normals ) );
+
+			}
+
+			if ( geometry.colors.length > 0 ) {
+
+				const colors = new Float32Array( geometry.colors.length * 3 );
+				buffergeometry.setAttribute( 'color', new THREE.BufferAttribute( colors, 3 ).copyColorsArray( geometry.colors ) );
+
+			}
+
+			if ( geometry.uvs.length > 0 ) {
+
+				const uvs = new Float32Array( geometry.uvs.length * 2 );
+				buffergeometry.setAttribute( 'uv', new THREE.BufferAttribute( uvs, 2 ).copyVector2sArray( geometry.uvs ) );
+
+			}
+
+			if ( geometry.uvs2.length > 0 ) {
+
+				const uvs2 = new Float32Array( geometry.uvs2.length * 2 );
+				buffergeometry.setAttribute( 'uv2', new THREE.BufferAttribute( uvs2, 2 ).copyVector2sArray( geometry.uvs2 ) );
+
+			} // groups
+
+
+			buffergeometry.groups = geometry.groups; // morphs
+
+			for ( const name in geometry.morphTargets ) {
+
+				const array = [];
+				const morphTargets = geometry.morphTargets[ name ];
+
+				for ( let i = 0, l = morphTargets.length; i < l; i ++ ) {
+
+					const morphTarget = morphTargets[ i ];
+					const attribute = new THREE.Float32BufferAttribute( morphTarget.data.length * 3, 3 );
+					attribute.name = morphTarget.name;
+					array.push( attribute.copyVector3sArray( morphTarget.data ) );
+
+				}
+
+				buffergeometry.morphAttributes[ name ] = array;
+
+			} // skinning
+
+
+			if ( geometry.skinIndices.length > 0 ) {
+
+				const skinIndices = new THREE.Float32BufferAttribute( geometry.skinIndices.length * 4, 4 );
+				buffergeometry.setAttribute( 'skinIndex', skinIndices.copyVector4sArray( geometry.skinIndices ) );
+
+			}
+
+			if ( geometry.skinWeights.length > 0 ) {
+
+				const skinWeights = new THREE.Float32BufferAttribute( geometry.skinWeights.length * 4, 4 );
+				buffergeometry.setAttribute( 'skinWeight', skinWeights.copyVector4sArray( geometry.skinWeights ) );
+
+			} //
+
+
+			if ( geometry.boundingSphere !== null ) {
+
+				buffergeometry.boundingSphere = geometry.boundingSphere.clone();
+
+			}
+
+			if ( geometry.boundingBox !== null ) {
+
+				buffergeometry.boundingBox = geometry.boundingBox.clone();
+
+			}
+
+			return buffergeometry;
+
+		},
+		computeTangents: function () {
+
+			console.error( 'THREE.Geometry: .computeTangents() has been removed.' );
+
+		},
+		computeLineDistances: function () {
+
+			console.error( 'THREE.Geometry: .computeLineDistances() has been removed. Use THREE.Line.computeLineDistances() instead.' );
+
+		},
+		applyMatrix: function ( matrix ) {
+
+			console.warn( 'THREE.Geometry: .applyMatrix() has been renamed to .applyMatrix4().' );
+			return this.applyMatrix4( matrix );
+
+		},
+		dispose: function () {
+
+			this.dispatchEvent( {
+				type: 'dispose'
+			} );
+
+		}
+	} );
+
+	Geometry.createBufferGeometryFromObject = function ( object ) {
+
+		let buffergeometry = new THREE.BufferGeometry();
+		const geometry = object.geometry;
+
+		if ( object.isPoints || object.isLine ) {
+
+			const positions = new THREE.Float32BufferAttribute( geometry.vertices.length * 3, 3 );
+			const colors = new THREE.Float32BufferAttribute( geometry.colors.length * 3, 3 );
+			buffergeometry.setAttribute( 'position', positions.copyVector3sArray( geometry.vertices ) );
+			buffergeometry.setAttribute( 'color', colors.copyColorsArray( geometry.colors ) );
+
+			if ( geometry.lineDistances && geometry.lineDistances.length === geometry.vertices.length ) {
+
+				const lineDistances = new THREE.Float32BufferAttribute( geometry.lineDistances.length, 1 );
+				buffergeometry.setAttribute( 'lineDistance', lineDistances.copyArray( geometry.lineDistances ) );
+
+			}
+
+			if ( geometry.boundingSphere !== null ) {
+
+				buffergeometry.boundingSphere = geometry.boundingSphere.clone();
+
+			}
+
+			if ( geometry.boundingBox !== null ) {
+
+				buffergeometry.boundingBox = geometry.boundingBox.clone();
+
+			}
+
+		} else if ( object.isMesh ) {
+
+			buffergeometry = geometry.toBufferGeometry();
+
+		}
+
+		return buffergeometry;
+
+	};
+
+	class DirectGeometry {
+
+		constructor() {
+
+			this.vertices = [];
+			this.normals = [];
+			this.colors = [];
+			this.uvs = [];
+			this.uvs2 = [];
+			this.groups = [];
+			this.morphTargets = {};
+			this.skinWeights = [];
+			this.skinIndices = []; // this.lineDistances = [];
+
+			this.boundingBox = null;
+			this.boundingSphere = null; // update flags
+
+			this.verticesNeedUpdate = false;
+			this.normalsNeedUpdate = false;
+			this.colorsNeedUpdate = false;
+			this.uvsNeedUpdate = false;
+			this.groupsNeedUpdate = false;
+
+		}
+
+		computeGroups( geometry ) {
+
+			const groups = [];
+			let group, i;
+			let materialIndex = undefined;
+			const faces = geometry.faces;
+
+			for ( i = 0; i < faces.length; i ++ ) {
+
+				const face = faces[ i ]; // materials
+
+				if ( face.materialIndex !== materialIndex ) {
+
+					materialIndex = face.materialIndex;
+
+					if ( group !== undefined ) {
+
+						group.count = i * 3 - group.start;
+						groups.push( group );
+
+					}
+
+					group = {
+						start: i * 3,
+						materialIndex: materialIndex
+					};
+
+				}
+
+			}
+
+			if ( group !== undefined ) {
+
+				group.count = i * 3 - group.start;
+				groups.push( group );
+
+			}
+
+			this.groups = groups;
+
+		}
+
+		fromGeometry( geometry ) {
+
+			const faces = geometry.faces;
+			const vertices = geometry.vertices;
+			const faceVertexUvs = geometry.faceVertexUvs;
+			const hasFaceVertexUv = faceVertexUvs[ 0 ] && faceVertexUvs[ 0 ].length > 0;
+			const hasFaceVertexUv2 = faceVertexUvs[ 1 ] && faceVertexUvs[ 1 ].length > 0; // morphs
+
+			const morphTargets = geometry.morphTargets;
+			const morphTargetsLength = morphTargets.length;
+			let morphTargetsPosition;
+
+			if ( morphTargetsLength > 0 ) {
+
+				morphTargetsPosition = [];
+
+				for ( let i = 0; i < morphTargetsLength; i ++ ) {
+
+					morphTargetsPosition[ i ] = {
+						name: morphTargets[ i ].name,
+						data: []
+					};
+
+				}
+
+				this.morphTargets.position = morphTargetsPosition;
+
+			}
+
+			const morphNormals = geometry.morphNormals;
+			const morphNormalsLength = morphNormals.length;
+			let morphTargetsNormal;
+
+			if ( morphNormalsLength > 0 ) {
+
+				morphTargetsNormal = [];
+
+				for ( let i = 0; i < morphNormalsLength; i ++ ) {
+
+					morphTargetsNormal[ i ] = {
+						name: morphNormals[ i ].name,
+						data: []
+					};
+
+				}
+
+				this.morphTargets.normal = morphTargetsNormal;
+
+			} // skins
+
+
+			const skinIndices = geometry.skinIndices;
+			const skinWeights = geometry.skinWeights;
+			const hasSkinIndices = skinIndices.length === vertices.length;
+			const hasSkinWeights = skinWeights.length === vertices.length; //
+
+			if ( vertices.length > 0 && faces.length === 0 ) {
+
+				console.error( 'THREE.DirectGeometry: Faceless geometries are not supported.' );
+
+			}
+
+			for ( let i = 0; i < faces.length; i ++ ) {
+
+				const face = faces[ i ];
+				this.vertices.push( vertices[ face.a ], vertices[ face.b ], vertices[ face.c ] );
+				const vertexNormals = face.vertexNormals;
+
+				if ( vertexNormals.length === 3 ) {
+
+					this.normals.push( vertexNormals[ 0 ], vertexNormals[ 1 ], vertexNormals[ 2 ] );
+
+				} else {
+
+					const normal = face.normal;
+					this.normals.push( normal, normal, normal );
+
+				}
+
+				const vertexColors = face.vertexColors;
+
+				if ( vertexColors.length === 3 ) {
+
+					this.colors.push( vertexColors[ 0 ], vertexColors[ 1 ], vertexColors[ 2 ] );
+
+				} else {
+
+					const color = face.color;
+					this.colors.push( color, color, color );
+
+				}
+
+				if ( hasFaceVertexUv === true ) {
+
+					const vertexUvs = faceVertexUvs[ 0 ][ i ];
+
+					if ( vertexUvs !== undefined ) {
+
+						this.uvs.push( vertexUvs[ 0 ], vertexUvs[ 1 ], vertexUvs[ 2 ] );
+
+					} else {
+
+						console.warn( 'THREE.DirectGeometry.fromGeometry(): Undefined vertexUv ', i );
+						this.uvs.push( new THREE.Vector2(), new THREE.Vector2(), new THREE.Vector2() );
+
+					}
+
+				}
+
+				if ( hasFaceVertexUv2 === true ) {
+
+					const vertexUvs = faceVertexUvs[ 1 ][ i ];
+
+					if ( vertexUvs !== undefined ) {
+
+						this.uvs2.push( vertexUvs[ 0 ], vertexUvs[ 1 ], vertexUvs[ 2 ] );
+
+					} else {
+
+						console.warn( 'THREE.DirectGeometry.fromGeometry(): Undefined vertexUv2 ', i );
+						this.uvs2.push( new THREE.Vector2(), new THREE.Vector2(), new THREE.Vector2() );
+
+					}
+
+				} // morphs
+
+
+				for ( let j = 0; j < morphTargetsLength; j ++ ) {
+
+					const morphTarget = morphTargets[ j ].vertices;
+					morphTargetsPosition[ j ].data.push( morphTarget[ face.a ], morphTarget[ face.b ], morphTarget[ face.c ] );
+
+				}
+
+				for ( let j = 0; j < morphNormalsLength; j ++ ) {
+
+					const morphNormal = morphNormals[ j ].vertexNormals[ i ];
+					morphTargetsNormal[ j ].data.push( morphNormal.a, morphNormal.b, morphNormal.c );
+
+				} // skins
+
+
+				if ( hasSkinIndices ) {
+
+					this.skinIndices.push( skinIndices[ face.a ], skinIndices[ face.b ], skinIndices[ face.c ] );
+
+				}
+
+				if ( hasSkinWeights ) {
+
+					this.skinWeights.push( skinWeights[ face.a ], skinWeights[ face.b ], skinWeights[ face.c ] );
+
+				}
+
+			}
+
+			this.computeGroups( geometry );
+			this.verticesNeedUpdate = geometry.verticesNeedUpdate;
+			this.normalsNeedUpdate = geometry.normalsNeedUpdate;
+			this.colorsNeedUpdate = geometry.colorsNeedUpdate;
+			this.uvsNeedUpdate = geometry.uvsNeedUpdate;
+			this.groupsNeedUpdate = geometry.groupsNeedUpdate;
+
+			if ( geometry.boundingSphere !== null ) {
+
+				this.boundingSphere = geometry.boundingSphere.clone();
+
+			}
+
+			if ( geometry.boundingBox !== null ) {
+
+				this.boundingBox = geometry.boundingBox.clone();
+
+			}
+
+			return this;
+
+		}
+
+	}
+
+	class Face3 {
+
+		constructor( a, b, c, normal, color, materialIndex = 0 ) {
+
+			this.a = a;
+			this.b = b;
+			this.c = c;
+			this.normal = normal && normal.isVector3 ? normal : new THREE.Vector3();
+			this.vertexNormals = Array.isArray( normal ) ? normal : [];
+			this.color = color && color.isColor ? color : new THREE.Color();
+			this.vertexColors = Array.isArray( color ) ? color : [];
+			this.materialIndex = materialIndex;
+
+		}
+
+		clone() {
+
+			return new this.constructor().copy( this );
+
+		}
+
+		copy( source ) {
+
+			this.a = source.a;
+			this.b = source.b;
+			this.c = source.c;
+			this.normal.copy( source.normal );
+			this.color.copy( source.color );
+			this.materialIndex = source.materialIndex;
+
+			for ( let i = 0, il = source.vertexNormals.length; i < il; i ++ ) {
+
+				this.vertexNormals[ i ] = source.vertexNormals[ i ].clone();
+
+			}
+
+			for ( let i = 0, il = source.vertexColors.length; i < il; i ++ ) {
+
+				this.vertexColors[ i ] = source.vertexColors[ i ].clone();
+
+			}
+
+			return this;
+
+		}
+
+	}
+
+	THREE.Face3 = Face3;
+	THREE.Geometry = Geometry;
+
+} )();

+ 67 - 123
examples/js/effects/AnaglyphEffect.js

@@ -1,151 +1,95 @@
-THREE.AnaglyphEffect = function ( renderer, width, height ) {
+( function () {
 
-	// Dubois matrices from https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.7.6968&rep=rep1&type=pdf#page=4
+	var AnaglyphEffect = function ( renderer, width, height ) {
 
-	this.colorMatrixLeft = new THREE.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.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 ] );
 
-	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 _camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
 
-	var _camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
+		var _scene = new THREE.Scene();
 
-	var _scene = new THREE.Scene();
+		var _stereo = new THREE.StereoCamera();
 
-	var _stereo = new THREE.StereoCamera();
+		var _params = {
+			minFilter: THREE.LinearFilter,
+			magFilter: THREE.NearestFilter,
+			format: THREE.RGBAFormat
+		};
+		if ( width === undefined ) width = 512;
+		if ( height === undefined ) height = 512;
 
-	var _params = { minFilter: THREE.LinearFilter, magFilter: THREE.NearestFilter, format: THREE.RGBAFormat };
+		var _renderTargetL = new THREE.WebGLRenderTarget( width, height, _params );
 
-	if ( width === undefined ) width = 512;
-	if ( height === undefined ) height = 512;
+		var _renderTargetR = new THREE.WebGLRenderTarget( width, height, _params );
 
-	var _renderTargetL = new THREE.WebGLRenderTarget( width, height, _params );
-	var _renderTargetR = new THREE.WebGLRenderTarget( width, height, _params );
+		var _material = new THREE.ShaderMaterial( {
+			uniforms: {
+				'mapLeft': {
+					value: _renderTargetL.texture
+				},
+				'mapRight': {
+					value: _renderTargetR.texture
+				},
+				'colorMatrixLeft': {
+					value: this.colorMatrixLeft
+				},
+				'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' )
+		} );
 
-	var _material = new THREE.ShaderMaterial( {
+		var _mesh = new THREE.Mesh( new THREE.PlaneGeometry( 2, 2 ), _material );
 
-		uniforms: {
+		_scene.add( _mesh );
 
-			'mapLeft': { value: _renderTargetL.texture },
-			'mapRight': { value: _renderTargetR.texture },
+		this.setSize = function ( width, height ) {
 
-			'colorMatrixLeft': { value: this.colorMatrixLeft },
-			'colorMatrixRight': { value: this.colorMatrixRight }
+			renderer.setSize( width, height );
+			var pixelRatio = renderer.getPixelRatio();
 
-		},
+			_renderTargetL.setSize( width * pixelRatio, height * pixelRatio );
 
-		vertexShader: [
+			_renderTargetR.setSize( width * pixelRatio, height * pixelRatio );
 
-			'varying vec2 vUv;',
+		};
 
-			'void main() {',
+		this.render = function ( scene, camera ) {
 
-			'	vUv = vec2( uv.x, uv.y );',
-			'	gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
+			var currentRenderTarget = renderer.getRenderTarget();
+			scene.updateMatrixWorld();
+			if ( camera.parent === null ) camera.updateMatrixWorld();
 
-			'}'
+			_stereo.update( camera );
 
-		].join( '\n' ),
+			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 );
 
-		fragmentShader: [
+		};
 
-			'uniform sampler2D mapLeft;',
-			'uniform sampler2D mapRight;',
-			'varying vec2 vUv;',
+		this.dispose = function () {
 
-			'uniform mat3 colorMatrixLeft;',
-			'uniform mat3 colorMatrixRight;',
+			if ( _renderTargetL ) _renderTargetL.dispose();
+			if ( _renderTargetR ) _renderTargetR.dispose();
+			if ( _mesh ) _mesh.geometry.dispose();
+			if ( _material ) _material.dispose();
 
-			// 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' )
-
-	} );
-
-	var _mesh = new THREE.Mesh( new THREE.PlaneGeometry( 2, 2 ), _material );
-	_scene.add( _mesh );
-
-	this.setSize = function ( width, height ) {
-
-		renderer.setSize( width, height );
-
-		var pixelRatio = renderer.getPixelRatio();
-
-		_renderTargetL.setSize( width * pixelRatio, height * pixelRatio );
-		_renderTargetR.setSize( width * pixelRatio, height * pixelRatio );
+		};
 
 	};
 
-	this.render = function ( scene, camera ) {
-
-		var currentRenderTarget = renderer.getRenderTarget();
-
-		scene.updateMatrixWorld();
-
-		if ( camera.parent === null ) camera.updateMatrixWorld();
-
-		_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 );
-
-	};
-
-	this.dispose = function () {
-
-		if ( _renderTargetL ) _renderTargetL.dispose();
-		if ( _renderTargetR ) _renderTargetR.dispose();
-		if ( _mesh ) _mesh.geometry.dispose();
-		if ( _material ) _material.dispose();
-
-	};
+	THREE.AnaglyphEffect = AnaglyphEffect;
 
-};
+} )();

+ 180 - 184
examples/js/effects/AsciiEffect.js

@@ -1,288 +1,284 @@
-/**
+( function () {
+
+	/**
  * Ascii generation is based on http://www.nihilogic.dk/labs/jsascii/
  * Maybe more about this later with a blog post at http://lab4games.net/zz85/blog
  *
  * 16 April 2012 - @blurspline
  */
+	var AsciiEffect = function ( renderer, charSet, options ) {
 
-THREE.AsciiEffect = function ( renderer, charSet, options ) {
-
-	// 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('');
-
-	if ( ! options ) options = {};
-
-	// Some ASCII settings
-
-	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
-
-	var strResolution = 'low';
-
-	var width, height;
+		// 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('');
 
-	var domElement = document.createElement( 'div' );
-	domElement.style.cursor = 'default';
+		if ( ! options ) options = {}; // Some ASCII settings
 
-	var oAscii = document.createElement( 'table' );
-	domElement.appendChild( oAscii );
+		var bResolution = ! options[ 'resolution' ] ? 0.15 : options[ 'resolution' ]; // Higher for more details
 
-	var iWidth, iHeight;
-	var oImg;
+		var iScale = ! options[ 'scale' ] ? 1 : options[ 'scale' ];
+		var bColor = ! options[ 'color' ] ? false : options[ 'color' ]; // nice but slows down rendering!
 
-	this.setSize = function ( w, h ) {
+		var bAlpha = ! options[ 'alpha' ] ? false : options[ 'alpha' ]; // Transparency
 
-		width = w;
-		height = h;
+		var bBlock = ! options[ 'block' ] ? false : options[ 'block' ]; // blocked characters. like good O dos
 
-		renderer.setSize( w, h );
+		var bInvert = ! options[ 'invert' ] ? false : options[ 'invert' ]; // black is white, white is black
 
-		initAsciiSize();
-
-	};
+		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.render = function ( scene, camera ) {
+			width = w;
+			height = h;
+			renderer.setSize( w, h );
+			initAsciiSize();
 
-		renderer.render( scene, camera );
-		asciifyImage( renderer, oAscii );
+		};
 
-	};
+		this.render = function ( scene, camera ) {
 
-	this.domElement = domElement;
+			renderer.render( scene, camera );
+			asciifyImage( renderer, oAscii );
 
+		};
 
-	// 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]
 	*/
 
-	function initAsciiSize() {
+		function initAsciiSize() {
 
-		iWidth = Math.round( width * fResolution );
-		iHeight = Math.round( height * fResolution );
+			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;
 
-		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;
+			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';
 
-		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';
+		}
 
-	}
+		var aDefaultCharList = ' .,:;i1tfLCG08@'.split( '' );
+		var aDefaultColorCharList = ' CGO08@'.split( '' );
+		var strFont = 'courier new, monospace';
+		var oCanvasImg = renderer.domElement;
+		var oCanvas = document.createElement( 'canvas' );
 
+		if ( ! oCanvas.getContext ) {
 
-	var aDefaultCharList = ( ' .,:;i1tfLCG08@' ).split( '' );
-	var aDefaultColorCharList = ( ' CGO08@' ).split( '' );
-	var strFont = 'courier new, monospace';
+			return;
 
-	var oCanvasImg = renderer.domElement;
+		}
 
-	var oCanvas = document.createElement( 'canvas' );
-	if ( ! oCanvas.getContext ) {
+		var oCtx = oCanvas.getContext( '2d' );
 
-		return;
+		if ( ! oCtx.getImageData ) {
 
-	}
+			return;
 
-	var oCtx = oCanvas.getContext( '2d' );
-	if ( ! oCtx.getImageData ) {
+		}
 
-		return;
+		var aCharList = bColor ? aDefaultColorCharList : aDefaultCharList;
+		if ( charSet ) aCharList = charSet;
+		var fResolution = 0.5;
 
-	}
+		switch ( strResolution ) {
 
-	var aCharList = ( bColor ? aDefaultColorCharList : aDefaultCharList );
+			case 'low':
+				fResolution = 0.25;
+				break;
 
-	if ( charSet ) aCharList = charSet;
+			case 'medium':
+				fResolution = 0.5;
+				break;
 
-	var fResolution = 0.5;
+			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; // Setup dom
 
-	}
+		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.
 
-	if ( bResolution ) fResolution = bResolution;
+		var fLetterSpacing = 0;
 
-	// Setup dom
+		if ( strResolution == 'low' ) {
 
-	var fFontSize = ( 2 / fResolution ) * iScale;
-	var fLineHeight = ( 2 / fResolution ) * iScale;
+			switch ( iScale ) {
 
-	// adjust letter-spacing for all combinations of scale and resolution to get it to fit the image width.
+				case 1:
+					fLetterSpacing = - 1;
+					break;
 
-	var fLetterSpacing = 0;
+				case 2:
+				case 3:
+					fLetterSpacing = - 2.1;
+					break;
 
-	if ( strResolution == 'low' ) {
+				case 4:
+					fLetterSpacing = - 3.1;
+					break;
 
-		switch ( iScale ) {
+				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;
 
-	if ( strResolution == 'high' ) {
+				case 3:
+					fLetterSpacing = - 1.04;
+					break;
 
-		switch ( iScale ) {
+				case 4:
+				case 5:
+					fLetterSpacing = - 2.1;
+					break;
 
-			case 1 :
-			case 2 : fLetterSpacing = 0; break;
-			case 3 :
-			case 4 :
-			case 5 : fLetterSpacing = - 1; break;
+			}
 
 		}
 
-	}
+		if ( strResolution == 'high' ) {
 
+			switch ( iScale ) {
 
-	// can't get a span or div to flow like an img element, but a table works?
+				case 1:
+				case 2:
+					fLetterSpacing = 0;
+					break;
 
+				case 3:
+				case 4:
+				case 5:
+					fLetterSpacing = - 1;
+					break;
 
-	// convert img element to ascii
+			}
 
-	function asciifyImage( canvasRenderer, oAscii ) {
+		} // can't get a span or div to flow like an img element, but a table works?
+		// convert img element to ascii
 
-		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
-		var strChars = '';
+		function asciifyImage( canvasRenderer, oAscii ) {
 
-		// console.time('rendering');
+			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
 
-		for ( var y = 0; y < iHeight; y += 2 ) {
+			var strChars = ''; // console.time('rendering');
 
-			for ( var x = 0; x < iWidth; x ++ ) {
+			for ( var y = 0; y < iHeight; y += 2 ) {
 
-				var iOffset = ( y * iWidth + x ) * 4;
+				for ( var x = 0; x < iWidth; x ++ ) {
 
-				var iRed = oImgData[ iOffset ];
-				var iGreen = oImgData[ iOffset + 1 ];
-				var iBlue = oImgData[ iOffset + 2 ];
-				var iAlpha = oImgData[ iOffset + 3 ];
-				var iCharIdx;
+					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;
 
-				var fBrightness;
+					if ( iAlpha == 0 ) {
 
-				fBrightness = ( 0.3 * iRed + 0.59 * iGreen + 0.11 * iBlue ) / 255;
-				// fBrightness = (0.3*iRed + 0.5*iGreen + 0.3*iBlue) / 255;
+						// should calculate alpha instead, but quick hack :)
+						//fBrightness *= (iAlpha / 255);
+						fBrightness = 1;
 
-				if ( iAlpha == 0 ) {
+					}
 
-					// should calculate alpha instead, but quick hack :)
-					//fBrightness *= (iAlpha / 255);
-					fBrightness = 1;
+					iCharIdx = Math.floor( ( 1 - fBrightness ) * ( aCharList.length - 1 ) );
 
-				}
+					if ( bInvert ) {
 
-				iCharIdx = Math.floor( ( 1 - fBrightness ) * ( aCharList.length - 1 ) );
+						iCharIdx = aCharList.length - iCharIdx - 1;
 
-				if ( bInvert ) {
+					} // good for debugging
+					//fBrightness = Math.floor(fBrightness * 10);
+					//strThisChar = fBrightness;
 
-					iCharIdx = aCharList.length - iCharIdx - 1;
 
-				}
+					var strThisChar = aCharList[ iCharIdx ];
+					if ( strThisChar === undefined || strThisChar == ' ' ) strThisChar = '&nbsp;';
 
-				// good for debugging
-				//fBrightness = Math.floor(fBrightness * 10);
-				//strThisChar = fBrightness;
+					if ( bColor ) {
 
-				var strThisChar = aCharList[ iCharIdx ];
+						strChars += '<span style=\'' + 'color:rgb(' + iRed + ',' + iGreen + ',' + iBlue + ');' + ( bBlock ? 'background-color:rgb(' + iRed + ',' + iGreen + ',' + iBlue + ');' : '' ) + ( bAlpha ? 'opacity:' + iAlpha / 255 + ';' : '' ) + '\'>' + strThisChar + '</span>';
 
-				if ( strThisChar === undefined || strThisChar == ' ' )
-					strThisChar = '&nbsp;';
+					} else {
 
-				if ( bColor ) {
+						strChars += strThisChar;
 
-					strChars += '<span style=\''
-						+ 'color:rgb(' + iRed + ',' + iGreen + ',' + iBlue + ');'
-						+ ( bBlock ? 'background-color:rgb(' + iRed + ',' + iGreen + ',' + iBlue + ');' : '' )
-						+ ( bAlpha ? 'opacity:' + ( iAlpha / 255 ) + ';' : '' )
-						+ '\'>' + strThisChar + '</span>';
-
-				} else {
-
-					strChars += strThisChar;
+					}
 
 				}
 
-			}
-
-			strChars += '<br/>';
+				strChars += '<br/>';
 
-		}
-
-		oAscii.innerHTML = '<tr><td>' + strChars + '</td></tr>';
+			}
 
-		// console.timeEnd('rendering');
+			oAscii.innerHTML = '<tr><td>' + strChars + '</td></tr>'; // console.timeEnd('rendering');
+			// return oAscii;
 
-		// return oAscii;
+		} // end modified asciifyImage block
 
-	}
+	};
 
-	// end modified asciifyImage block
+	THREE.AsciiEffect = AsciiEffect;
 
-};
+} )();

+ 258 - 349
examples/js/effects/OutlineEffect.js

@@ -1,11 +1,13 @@
-/**
+( function () {
+
+	/**
  * Reference: https://en.wikipedia.org/wiki/Cel_shading
  *
  * API
  *
  * 1. Traditional
  *
- * var effect = new THREE.OutlineEffect( renderer );
+ * var effect = new OutlineEffect( renderer );
  *
  * function render() {
  *
@@ -15,7 +17,7 @@
  *
  * 2. VR compatible
  *
- * var effect = new THREE.OutlineEffect( renderer );
+ * var effect = new OutlineEffect( renderer );
  * var renderingOutline = false;
  *
  * scene.onAfterRender = function () {
@@ -37,7 +39,7 @@
  * }
  *
  * // How to set default outline parameters
- * new THREE.OutlineEffect( renderer, {
+ * new OutlineEffect( renderer, {
  * 	defaultThickness: 0.01,
  * 	defaultColor: [ 0, 0, 0 ],
  * 	defaultAlpha: 0.8,
@@ -54,512 +56,419 @@
  * };
  */
 
-THREE.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 }
-	};
-
-	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 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
+	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
+			}
+		};
+		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
+			} );
 
-		'	gl_Position = calculateOutline( gl_Position, outlineNormal, vec4( transformed, 1.0 ) );',
+		}
 
-		'	#include <logdepthbuf_vertex>',
-		'	#include <clipping_planes_vertex>',
-		'	#include <fog_vertex>',
+		function getOutlineMaterialFromCache( originalMaterial ) {
 
-		'}',
+			var data = cache[ originalMaterial.uuid ];
 
-	].join( '\n' );
+			if ( data === undefined ) {
 
-	var fragmentShader = [
+				data = {
+					material: createMaterial(),
+					used: true,
+					keepAlive: defaultKeepAlive,
+					count: 0
+				};
+				cache[ originalMaterial.uuid ] = data;
 
-		'#include <common>',
-		'#include <fog_pars_fragment>',
-		'#include <logdepthbuf_pars_fragment>',
-		'#include <clipping_planes_pars_fragment>',
+			}
 
-		'uniform vec3 outlineColor;',
-		'uniform float outlineAlpha;',
+			data.used = true;
+			return data.material;
 
-		'void main() {',
+		}
 
-		'	#include <clipping_planes_fragment>',
-		'	#include <logdepthbuf_fragment>',
+		function getOutlineMaterial( originalMaterial ) {
 
-		'	gl_FragColor = vec4( outlineColor, outlineAlpha );',
+			var outlineMaterial = getOutlineMaterialFromCache( originalMaterial );
+			originalMaterials[ outlineMaterial.uuid ] = originalMaterial;
+			updateOutlineMaterial( outlineMaterial, originalMaterial );
+			return outlineMaterial;
 
-		'	#include <tonemapping_fragment>',
-		'	#include <encodings_fragment>',
-		'	#include <fog_fragment>',
-		'	#include <premultiplied_alpha_fragment>',
+		}
 
-		'}'
+		function isCompatible( object ) {
 
-	].join( '\n' );
+			var geometry = object.geometry;
+			var hasNormals = false;
 
-	function createMaterial() {
+			if ( object.geometry !== undefined ) {
 
-		return new THREE.ShaderMaterial( {
-			type: 'OutlineEffect',
-			uniforms: THREE.UniformsUtils.merge( [
-				THREE.UniformsLib[ 'fog' ],
-				THREE.UniformsLib[ 'displacementmap' ],
-				uniformsOutline
-			] ),
-			vertexShader: vertexShader,
-			fragmentShader: fragmentShader,
-			side: THREE.BackSide
-		} );
+				if ( geometry.isBufferGeometry ) {
 
-	}
+					hasNormals = geometry.attributes.normal !== undefined;
 
-	function getOutlineMaterialFromCache( originalMaterial ) {
+				} else {
 
-		var data = cache[ originalMaterial.uuid ];
+					hasNormals = true; // the renderer always produces a normal attribute for Geometry
 
-		if ( data === undefined ) {
+				}
 
-			data = {
-				material: createMaterial(),
-				used: true,
-				keepAlive: defaultKeepAlive,
-				count: 0
-			};
+			}
 
-			cache[ originalMaterial.uuid ] = data;
+			return object.isMesh === true && object.material !== undefined && hasNormals === true;
 
 		}
 
-		data.used = true;
-
-		return data.material;
+		function setOutlineMaterial( object ) {
 
-	}
+			if ( isCompatible( object ) === false ) return;
 
-	function getOutlineMaterial( originalMaterial ) {
+			if ( Array.isArray( object.material ) ) {
 
-		var outlineMaterial = getOutlineMaterialFromCache( originalMaterial );
+				for ( var i = 0, il = object.material.length; i < il; i ++ ) {
 
-		originalMaterials[ outlineMaterial.uuid ] = originalMaterial;
+					object.material[ i ] = getOutlineMaterial( object.material[ i ] );
 
-		updateOutlineMaterial( outlineMaterial, originalMaterial );
-
-		return outlineMaterial;
-
-	}
-
-	function isCompatible( object ) {
-
-		var geometry = object.geometry;
-		var hasNormals = false;
-
-		if ( object.geometry !== undefined ) {
-
-			if ( geometry.isBufferGeometry ) {
-
-				hasNormals = geometry.attributes.normal !== undefined;
+				}
 
 			} else {
 
-				hasNormals = true; // the renderer always produces a normal attribute for Geometry
-
-			}
-
-		}
-
-		return ( object.isMesh === true && object.material !== undefined && hasNormals === true );
-
-	}
-
-	function setOutlineMaterial( object ) {
-
-		if ( isCompatible( object ) === false ) return;
-
-		if ( Array.isArray( object.material ) ) {
-
-			for ( var i = 0, il = object.material.length; i < il; i ++ ) {
-
-				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 ( var 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 ) {
 
-	}
+			var 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;
+		function updateUniforms( material, originalMaterial ) {
 
-		updateUniforms( material, originalMaterial );
+			var outlineParameters = originalMaterial.userData.outlineParameters;
+			material.uniforms.outlineAlpha.value = originalMaterial.opacity;
 
-	}
+			if ( outlineParameters !== undefined ) {
 
-	function updateUniforms( material, originalMaterial ) {
+				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;
 
-		var outlineParameters = originalMaterial.userData.outlineParameters;
+			}
 
-		material.uniforms.outlineAlpha.value = originalMaterial.opacity;
+			if ( originalMaterial.displacementMap ) {
 
-		if ( outlineParameters !== undefined ) {
+				material.uniforms.displacementMap.value = originalMaterial.displacementMap;
+				material.uniforms.displacementScale.value = originalMaterial.displacementScale;
+				material.uniforms.displacementBias.value = originalMaterial.displacementBias;
 
-			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 ) {
-
-			material.uniforms.displacementMap.value = originalMaterial.displacementMap;
-			material.uniforms.displacementScale.value = originalMaterial.displacementScale;
-			material.uniforms.displacementBias.value = originalMaterial.displacementBias;
-
-		}
+		function updateOutlineMaterial( material, originalMaterial ) {
 
-	}
+			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;
 
-	function updateOutlineMaterial( material, originalMaterial ) {
+			if ( outlineParameters !== undefined ) {
 
-		if ( material.name === 'invisible' ) return;
+				if ( originalMaterial.visible === false ) {
 
-		var outlineParameters = originalMaterial.userData.outlineParameters;
+					material.visible = false;
 
-		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 {
+			if ( originalMaterial.clippingPlanes ) {
 
-			material.transparent = originalMaterial.transparent;
-			material.visible = originalMaterial.visible;
+				material.clipping = true;
+				material.clippingPlanes = originalMaterial.clippingPlanes;
+				material.clipIntersection = originalMaterial.clipIntersection;
+				material.clipShadows = originalMaterial.clipShadows;
 
-		}
+			}
 
-		if ( originalMaterial.wireframe === true || originalMaterial.depthTest === false ) material.visible = false;
+			material.version = originalMaterial.version; // update outline material if necessary
 
-		if ( originalMaterial.clippingPlanes ) {
+		}
 
-			material.clipping = true;
+		function cleanupCache() {
 
-			material.clippingPlanes = originalMaterial.clippingPlanes;
-			material.clipIntersection = originalMaterial.clipIntersection;
-			material.clipShadows = originalMaterial.clipShadows;
+			var keys; // clear originialMaterials
 
-		}
+			keys = Object.keys( originalMaterials );
 
-		material.version = originalMaterial.version; // update outline material if necessary
+			for ( var i = 0, il = keys.length; i < il; i ++ ) {
 
-	}
+				originalMaterials[ keys[ i ] ] = undefined;
 
-	function cleanupCache() {
+			} // clear originalOnBeforeRenders
 
-		var keys;
 
-		// clear originialMaterials
-		keys = Object.keys( originalMaterials );
+			keys = Object.keys( originalOnBeforeRenders );
 
-		for ( var i = 0, il = keys.length; i < il; i ++ ) {
+			for ( var i = 0, il = keys.length; i < il; i ++ ) {
 
-			originalMaterials[ keys[ i ] ] = undefined;
+				originalOnBeforeRenders[ keys[ i ] ] = undefined;
 
-		}
+			} // remove unused outlineMaterial from cache
 
-		// clear originalOnBeforeRenders
-		keys = Object.keys( originalOnBeforeRenders );
 
-		for ( var i = 0, il = keys.length; i < il; i ++ ) {
+			keys = Object.keys( cache );
 
-			originalOnBeforeRenders[ keys[ i ] ] = undefined;
+			for ( var i = 0, il = keys.length; i < il; i ++ ) {
 
-		}
+				var key = keys[ i ];
 
-		// remove unused outlineMaterial from cache
-		keys = Object.keys( cache );
+				if ( cache[ key ].used === false ) {
 
-		for ( var i = 0, il = keys.length; i < il; i ++ ) {
+					cache[ key ].count ++;
 
-			var key = keys[ i ];
+					if ( cache[ key ].keepAlive === false && cache[ key ].count > removeThresholdCount ) {
 
-			if ( cache[ key ].used === false ) {
+						delete cache[ key ];
 
-				cache[ key ].count ++;
+					}
 
-				if ( cache[ key ].keepAlive === false && cache[ key ].count > removeThresholdCount ) {
+				} else {
 
-					delete cache[ key ];
+					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 ) {
+			var renderTarget;
+			var 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 ( this.enabled === false ) {
 
-		if ( forceClear ) renderer.clear();
+				renderer.render( scene, camera );
+				return;
 
-		if ( this.enabled === false ) {
+			}
 
+			var currentAutoClear = renderer.autoClear;
+			renderer.autoClear = this.autoClear;
 			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 ) {
-
-		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;
-
-	};
-
-	/*
+			renderer.autoClear = currentAutoClear;
+			this.renderOutline( scene, camera );
+
+		};
+
+		this.renderOutline = function ( scene, camera ) {
+
+			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
-	 * THREE.OutlineEffect to be called from other *Effect, like
+	 * OutlineEffect to be called from other *Effect, like
 	 *
-	 * effect = new THREE.StereoEffect( new THREE.OutlineEffect( renderer ) );
+	 * effect = new StereoEffect( new OutlineEffect( renderer ) );
 	 *
 	 * function render () {
 	 *
- 	 * 	effect.render( scene, camera );
+		 * 	effect.render( scene, camera );
 	 *
 	 * }
 	 */
-	this.autoClear = renderer.autoClear;
-	this.domElement = renderer.domElement;
-	this.shadowMap = renderer.shadowMap;
 
-	this.clear = function ( color, depth, stencil ) {
 
-		renderer.clear( color, depth, stencil );
+		this.autoClear = renderer.autoClear;
+		this.domElement = renderer.domElement;
+		this.shadowMap = renderer.shadowMap;
 
-	};
+		this.clear = function ( color, depth, stencil ) {
 
-	this.getPixelRatio = function () {
+			renderer.clear( color, depth, stencil );
 
-		return renderer.getPixelRatio();
+		};
 
-	};
+		this.getPixelRatio = function () {
 
-	this.setPixelRatio = function ( value ) {
+			return renderer.getPixelRatio();
 
-		renderer.setPixelRatio( value );
+		};
 
-	};
+		this.setPixelRatio = function ( value ) {
 
-	this.getSize = function ( target ) {
+			renderer.setPixelRatio( value );
 
-		return renderer.getSize( target );
+		};
 
-	};
+		this.getSize = function ( target ) {
 
-	this.setSize = function ( width, height, updateStyle ) {
+			return renderer.getSize( target );
 
-		renderer.setSize( width, height, updateStyle );
+		};
 
-	};
+		this.setSize = function ( width, height, updateStyle ) {
 
-	this.setViewport = function ( x, y, width, height ) {
+			renderer.setSize( width, height, updateStyle );
 
-		renderer.setViewport( x, y, width, height );
+		};
 
-	};
+		this.setViewport = function ( x, y, width, height ) {
 
-	this.setScissor = function ( x, y, width, height ) {
+			renderer.setViewport( x, y, width, height );
 
-		renderer.setScissor( x, y, width, height );
+		};
 
-	};
+		this.setScissor = function ( x, y, width, height ) {
 
-	this.setScissorTest = function ( boolean ) {
+			renderer.setScissor( x, y, width, height );
 
-		renderer.setScissorTest( boolean );
+		};
 
-	};
+		this.setScissorTest = function ( boolean ) {
+
+			renderer.setScissorTest( boolean );
 
-	this.setRenderTarget = function ( renderTarget ) {
+		};
 
-		renderer.setRenderTarget( renderTarget );
+		this.setRenderTarget = function ( renderTarget ) {
+
+			renderer.setRenderTarget( renderTarget );
+
+		};
 
 	};
 
-};
+	THREE.OutlineEffect = OutlineEffect;
+
+} )();

+ 47 - 73
examples/js/effects/ParallaxBarrierEffect.js

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

+ 106 - 86
examples/js/effects/PeppersGhostEffect.js

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

+ 29 - 27
examples/js/effects/StereoEffect.js

@@ -1,44 +1,46 @@
-THREE.StereoEffect = function ( renderer ) {
+( function () {
 
-	var _stereo = new THREE.StereoCamera();
-	_stereo.aspect = 0.5;
-	var size = new THREE.Vector2();
+	var StereoEffect = function ( renderer ) {
 
-	this.setEyeSeparation = function ( eyeSep ) {
+		var _stereo = new THREE.StereoCamera();
 
-		_stereo.eyeSep = eyeSep;
+		_stereo.aspect = 0.5;
+		var size = new THREE.Vector2();
 
-	};
-
-	this.setSize = function ( width, height ) {
+		this.setEyeSeparation = function ( eyeSep ) {
 
-		renderer.setSize( width, height );
+			_stereo.eyeSep = eyeSep;
 
-	};
+		};
 
-	this.render = function ( scene, camera ) {
+		this.setSize = function ( width, height ) {
 
-		scene.updateMatrixWorld();
+			renderer.setSize( width, height );
 
-		if ( camera.parent === null ) camera.updateMatrixWorld();
+		};
 
-		_stereo.update( camera );
+		this.render = function ( scene, camera ) {
 
-		renderer.getSize( size );
+			scene.updateMatrixWorld();
+			if ( camera.parent === null ) camera.updateMatrixWorld();
 
-		if ( renderer.autoClear ) renderer.clear();
-		renderer.setScissorTest( true );
+			_stereo.update( camera );
 
-		renderer.setScissor( 0, 0, size.width / 2, size.height );
-		renderer.setViewport( 0, 0, size.width / 2, size.height );
-		renderer.render( scene, _stereo.cameraL );
+			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 );
 
-		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;
+
+} )();

+ 53 - 0
examples/js/environments/DebugEnvironment.js

@@ -0,0 +1,53 @@
+( function () {
+
+	class DebugEnvironment extends THREE.Scene {
+
+		constructor() {
+
+			super();
+			const geometry = new THREE.BoxGeometry();
+			geometry.deleteAttribute( 'uv' );
+			const roomMaterial = new THREE.MeshStandardMaterial( {
+				metalness: 0,
+				side: THREE.BackSide
+			} );
+			const room = new THREE.Mesh( geometry, roomMaterial );
+			room.scale.setScalar( 10 );
+			this.add( room );
+			const mainLight = new THREE.PointLight( 0xffffff, 50, 0, 2 );
+			this.add( mainLight );
+			const material1 = new THREE.MeshLambertMaterial( {
+				color: 0xff0000,
+				emissive: 0xffffff,
+				emissiveIntensity: 10
+			} );
+			const light1 = new THREE.Mesh( geometry, material1 );
+			light1.position.set( - 5, 2, 0 );
+			light1.scale.set( 0.1, 1, 1 );
+			this.add( light1 );
+			const material2 = new THREE.MeshLambertMaterial( {
+				color: 0x00ff00,
+				emissive: 0xffffff,
+				emissiveIntensity: 10
+			} );
+			const light2 = new THREE.Mesh( geometry, material2 );
+			light2.position.set( 0, 5, 0 );
+			light2.scale.set( 1, 0.1, 1 );
+			this.add( light2 );
+			const material3 = new THREE.MeshLambertMaterial( {
+				color: 0x0000ff,
+				emissive: 0xffffff,
+				emissiveIntensity: 10
+			} );
+			const light3 = new THREE.Mesh( geometry, material3 );
+			light3.position.set( 2, 1, 5 );
+			light3.scale.set( 1.5, 2, 0.1 );
+			this.add( light3 );
+
+		}
+
+	}
+
+	THREE.DebugEnvironment = DebugEnvironment;
+
+} )();

+ 100 - 0
examples/js/environments/RoomEnvironment.js

@@ -0,0 +1,100 @@
+( function () {
+
+	/**
+ * https://github.com/google/model-viewer/blob/master/packages/model-viewer/src/three-components/EnvironmentScene.ts
+ */
+
+	class RoomEnvironment extends THREE.Scene {
+
+		constructor() {
+
+			super();
+			const geometry = new THREE.BoxGeometry();
+			geometry.deleteAttribute( 'uv' );
+			const roomMaterial = new THREE.MeshStandardMaterial( {
+				side: THREE.BackSide
+			} );
+			const boxMaterial = new THREE.MeshStandardMaterial();
+			const mainLight = new THREE.PointLight( 0xffffff, 5.0, 28, 2 );
+			mainLight.position.set( 0.418, 16.199, 0.300 );
+			this.add( mainLight );
+			const room = new THREE.Mesh( geometry, roomMaterial );
+			room.position.set( - 0.757, 13.219, 0.717 );
+			room.scale.set( 31.713, 28.305, 28.591 );
+			this.add( room );
+			const box1 = new THREE.Mesh( geometry, boxMaterial );
+			box1.position.set( - 10.906, 2.009, 1.846 );
+			box1.rotation.set( 0, - 0.195, 0 );
+			box1.scale.set( 2.328, 7.905, 4.651 );
+			this.add( box1 );
+			const box2 = new THREE.Mesh( geometry, boxMaterial );
+			box2.position.set( - 5.607, - 0.754, - 0.758 );
+			box2.rotation.set( 0, 0.994, 0 );
+			box2.scale.set( 1.970, 1.534, 3.955 );
+			this.add( box2 );
+			const box3 = new THREE.Mesh( geometry, boxMaterial );
+			box3.position.set( 6.167, 0.857, 7.803 );
+			box3.rotation.set( 0, 0.561, 0 );
+			box3.scale.set( 3.927, 6.285, 3.687 );
+			this.add( box3 );
+			const box4 = new THREE.Mesh( geometry, boxMaterial );
+			box4.position.set( - 2.017, 0.018, 6.124 );
+			box4.rotation.set( 0, 0.333, 0 );
+			box4.scale.set( 2.002, 4.566, 2.064 );
+			this.add( box4 );
+			const box5 = new THREE.Mesh( geometry, boxMaterial );
+			box5.position.set( 2.291, - 0.756, - 2.621 );
+			box5.rotation.set( 0, - 0.286, 0 );
+			box5.scale.set( 1.546, 1.552, 1.496 );
+			this.add( box5 );
+			const box6 = new THREE.Mesh( geometry, boxMaterial );
+			box6.position.set( - 2.193, - 0.369, - 5.547 );
+			box6.rotation.set( 0, 0.516, 0 );
+			box6.scale.set( 3.875, 3.487, 2.986 );
+			this.add( box6 ); // -x right
+
+			const light1 = new THREE.Mesh( geometry, createAreaLightMaterial( 50 ) );
+			light1.position.set( - 16.116, 14.37, 8.208 );
+			light1.scale.set( 0.1, 2.428, 2.739 );
+			this.add( light1 ); // -x left
+
+			const light2 = new THREE.Mesh( geometry, createAreaLightMaterial( 50 ) );
+			light2.position.set( - 16.109, 18.021, - 8.207 );
+			light2.scale.set( 0.1, 2.425, 2.751 );
+			this.add( light2 ); // +x
+
+			const light3 = new THREE.Mesh( geometry, createAreaLightMaterial( 17 ) );
+			light3.position.set( 14.904, 12.198, - 1.832 );
+			light3.scale.set( 0.15, 4.265, 6.331 );
+			this.add( light3 ); // +z
+
+			const light4 = new THREE.Mesh( geometry, createAreaLightMaterial( 43 ) );
+			light4.position.set( - 0.462, 8.89, 14.520 );
+			light4.scale.set( 4.38, 5.441, 0.088 );
+			this.add( light4 ); // -z
+
+			const light5 = new THREE.Mesh( geometry, createAreaLightMaterial( 20 ) );
+			light5.position.set( 3.235, 11.486, - 12.541 );
+			light5.scale.set( 2.5, 2.0, 0.1 );
+			this.add( light5 ); // +y
+
+			const light6 = new THREE.Mesh( geometry, createAreaLightMaterial( 100 ) );
+			light6.position.set( 0.0, 20.0, 0.0 );
+			light6.scale.set( 1.0, 0.1, 1.0 );
+			this.add( light6 );
+
+		}
+
+	}
+
+	function createAreaLightMaterial( intensity ) {
+
+		const material = new THREE.MeshBasicMaterial();
+		material.color.setScalar( intensity );
+		return material;
+
+	}
+
+	THREE.RoomEnvironment = RoomEnvironment;
+
+} )();

+ 283 - 477
examples/js/exporters/ColladaExporter.js

@@ -1,60 +1,56 @@
-/**
+( function () {
+
+	/**
  * https://github.com/gkjohnson/collada-exporter-js
  *
  * Usage:
- *  var exporter = new THREE.ColladaExporter();
+ *	var exporter = new ColladaExporter();
  *
- *  var data = exporter.parse(mesh);
+ *	var data = exporter.parse(mesh);
  *
  * Format Definition:
- *  https://www.khronos.org/collada/
+ *	https://www.khronos.org/collada/
  */
 
-THREE.ColladaExporter = function () {};
+	var ColladaExporter = function () {};
 
-THREE.ColladaExporter.prototype = {
+	ColladaExporter.prototype = {
+		constructor: ColladaExporter,
+		parse: function ( object, onDone, options ) {
 
-	constructor: THREE.ColladaExporter,
+			options = options || {};
+			options = Object.assign( {
+				version: '1.4.1',
+				author: null,
+				textureDirectory: ''
+			}, options );
 
-	parse: function ( object, onDone, options ) {
+			if ( options.textureDirectory !== '' ) {
 
-		options = options || {};
+				options.textureDirectory = `${options.textureDirectory}/`.replace( /\\/g, '/' ).replace( /\/+/g, '/' );
 
-		options = Object.assign( {
-			version: '1.4.1',
-			author: null,
-			textureDirectory: '',
-		}, options );
+			}
 
-		if ( options.textureDirectory !== '' ) {
+			var version = options.version;
 
-			options.textureDirectory = `${ options.textureDirectory }/`
-				.replace( /\\/g, '/' )
-				.replace( /\/+/g, '/' );
+			if ( version !== '1.4.1' && version !== '1.5.0' ) {
 
-		}
+				console.warn( `ColladaExporter : Version ${version} not supported for export. Only 1.4.1 and 1.5.0.` );
+				return null;
 
-		var version = options.version;
-		if ( version !== '1.4.1' && version !== '1.5.0' ) {
+			} // Convert the urdf xml into a well-formatted, indented format
 
-			console.warn( `ColladaExporter : Version ${ version } not supported for export. Only 1.4.1 and 1.5.0.` );
-			return null;
-
-		}
 
-		// Convert the urdf xml into a well-formatted, indented format
-		function format( urdf ) {
+			function format( urdf ) {
 
-			var IS_END_TAG = /^<\//;
-			var IS_SELF_CLOSING = /(\?>$)|(\/>$)/;
-			var HAS_TEXT = /<[^>]+>[^<]*<\/[^<]+>/;
+				var IS_END_TAG = /^<\//;
+				var IS_SELF_CLOSING = /(\?>$)|(\/>$)/;
+				var HAS_TEXT = /<[^>]+>[^<]*<\/[^<]+>/;
 
-			var pad = ( ch, num ) => ( num > 0 ? ch + pad( ch, num - 1 ) : '' );
+				var pad = ( ch, num ) => num > 0 ? ch + pad( ch, num - 1 ) : '';
 
-			var tagnum = 0;
-			return urdf
-				.match( /(<[^>]+>[^<]+<\/[^<]+>)|(<[^>]+>)/g )
-				.map( tag => {
+				var tagnum = 0;
+				return urdf.match( /(<[^>]+>[^<]+<\/[^<]+>)|(<[^>]+>)/g ).map( tag => {
 
 					if ( ! HAS_TEXT.test( tag ) && ! IS_SELF_CLOSING.test( tag ) && IS_END_TAG.test( tag ) ) {
 
@@ -62,7 +58,7 @@ THREE.ColladaExporter.prototype = {
 
 					}
 
-					var res = `${ pad( '  ', tagnum ) }${ tag }`;
+					var res = `${pad( '	', tagnum )}${tag}`;
 
 					if ( ! HAS_TEXT.test( tag ) && ! IS_SELF_CLOSING.test( tag ) && ! IS_END_TAG.test( tag ) ) {
 
@@ -72,592 +68,402 @@ THREE.ColladaExporter.prototype = {
 
 					return res;
 
-				} )
-				.join( '\n' );
+				} ).join( '\n' );
 
-		}
+			} // Convert an image into a png format for saving
+
+
+			function base64ToBuffer( str ) {
 
-		// Convert an image into a png format for saving
-		function base64ToBuffer( str ) {
+				var b = atob( str );
+				var buf = new Uint8Array( b.length );
 
-			var b = atob( str );
-			var buf = new Uint8Array( b.length );
+				for ( var i = 0, l = buf.length; i < l; i ++ ) {
 
-			for ( var i = 0, l = buf.length; i < l; i ++ ) {
+					buf[ i ] = b.charCodeAt( i );
+
+				}
 
-				buf[ i ] = b.charCodeAt( i );
+				return buf;
 
 			}
 
-			return buf;
+			var canvas, ctx;
 
-		}
+			function imageToData( image, ext ) {
 
-		var canvas, ctx;
-		function imageToData( image, ext ) {
+				canvas = canvas || document.createElement( 'canvas' );
+				ctx = ctx || canvas.getContext( '2d' );
+				canvas.width = image.width;
+				canvas.height = image.height;
+				ctx.drawImage( image, 0, 0 ); // Get the base64 encoded data
 
-			canvas = canvas || document.createElement( 'canvas' );
-			ctx = ctx || canvas.getContext( '2d' );
+				var base64data = canvas.toDataURL( `image/${ext}`, 1 ).replace( /^data:image\/(png|jpg);base64,/, '' ); // Convert to a uint8 array
 
-			canvas.width = image.width;
-			canvas.height = image.height;
+				return base64ToBuffer( base64data );
 
-			ctx.drawImage( image, 0, 0 );
+			} // gets the attribute array. Generate a new array if the attribute is interleaved
 
-			// Get the base64 encoded data
-			var base64data = canvas
-				.toDataURL( `image/${ ext }`, 1 )
-				.replace( /^data:image\/(png|jpg);base64,/, '' );
 
-			// Convert to a uint8 array
-			return base64ToBuffer( base64data );
+			var getFuncs = [ 'getX', 'getY', 'getZ', 'getW' ];
 
-		}
+			function attrBufferToArray( attr ) {
+
+				if ( attr.isInterleavedBufferAttribute ) {
 
-		// gets the attribute array. Generate a new array if the attribute is interleaved
-		var getFuncs = [ 'getX', 'getY', 'getZ', 'getW' ];
-		function attrBufferToArray( attr ) {
+					// use the typed array constructor to save on memory
+					var arr = new attr.array.constructor( attr.count * attr.itemSize );
+					var size = attr.itemSize;
 
-			if ( attr.isInterleavedBufferAttribute ) {
+					for ( var i = 0, l = attr.count; i < l; i ++ ) {
 
-				// use the typed array constructor to save on memory
-				var arr = new attr.array.constructor( attr.count * attr.itemSize );
-				var size = attr.itemSize;
-				for ( var i = 0, l = attr.count; i < l; i ++ ) {
+						for ( var j = 0; j < size; j ++ ) {
 
-					for ( var j = 0; j < size; j ++ ) {
+							arr[ i * size + j ] = attr[ getFuncs[ j ] ]( i );
 
-						arr[ i * size + j ] = attr[ getFuncs[ j ] ]( i );
+						}
 
 					}
 
-				}
+					return arr;
 
-				return arr;
+				} else {
 
-			} else {
+					return attr.array;
 
-				return attr.array;
+				}
 
-			}
+			} // Returns an array of the same type starting at the `st` index,
+			// and `ct` length
 
-		}
 
-		// Returns an array of the same type starting at the `st` index,
-		// and `ct` length
-		function subArray( arr, st, ct ) {
+			function subArray( arr, st, ct ) {
 
-			if ( Array.isArray( arr ) ) return arr.slice( st, st + ct );
-			else return new arr.constructor( arr.buffer, st * arr.BYTES_PER_ELEMENT, ct );
+				if ( Array.isArray( arr ) ) return arr.slice( st, st + ct ); else return new arr.constructor( arr.buffer, st * arr.BYTES_PER_ELEMENT, ct );
 
-		}
+			} // Returns the string for a geometry's attribute
 
-		// Returns the string for a geometry's attribute
-		function getAttribute( attr, name, params, type ) {
 
-			var array = attrBufferToArray( attr );
-			var res =
-					`<source id="${ name }">` +
+			function getAttribute( attr, name, params, type ) {
 
-					`<float_array id="${ name }-array" count="${ array.length }">` +
-					array.join( ' ' ) +
-					'</float_array>' +
+				var array = attrBufferToArray( attr );
+				var res = `<source id="${name}">` + `<float_array id="${name}-array" count="${array.length}">` + array.join( ' ' ) + '</float_array>' + '<technique_common>' + `<accessor source="#${name}-array" count="${Math.floor( array.length / attr.itemSize )}" stride="${attr.itemSize}">` + params.map( n => `<param name="${n}" type="${type}" />` ).join( '' ) + '</accessor>' + '</technique_common>' + '</source>';
+				return res;
 
-					'<technique_common>' +
-					`<accessor source="#${ name }-array" count="${ Math.floor( array.length / attr.itemSize ) }" stride="${ attr.itemSize }">` +
+			} // Returns the string for a node's transform information
 
-					params.map( n => `<param name="${ n }" type="${ type }" />` ).join( '' ) +
 
-					'</accessor>' +
-					'</technique_common>' +
-					'</source>';
+			var transMat;
 
-			return res;
+			function getTransform( o ) {
 
-		}
+				// ensure the object's matrix is up to date
+				// before saving the transform
+				o.updateMatrix();
+				transMat = transMat || new THREE.Matrix4();
+				transMat.copy( o.matrix );
+				transMat.transpose();
+				return `<matrix>${transMat.toArray().join( ' ' )}</matrix>`;
 
-		// Returns the string for a node's transform information
-		var transMat;
-		function getTransform( o ) {
+			} // Process the given piece of geometry into the geometry library
+			// Returns the mesh id
 
-			// ensure the object's matrix is up to date
-			// before saving the transform
-			o.updateMatrix();
 
-			transMat = transMat || new THREE.Matrix4();
-			transMat.copy( o.matrix );
-			transMat.transpose();
-			return `<matrix>${ transMat.toArray().join( ' ' ) }</matrix>`;
+			function processGeometry( g ) {
 
-		}
+				var info = geometryInfo.get( g );
 
-		// Process the given piece of geometry into the geometry library
-		// Returns the mesh id
-		function processGeometry( g ) {
+				if ( ! info ) {
 
-			var info = geometryInfo.get( g );
+					// convert the geometry to bufferGeometry if it isn't already
+					var bufferGeometry = g;
 
-			if ( ! info ) {
+					if ( bufferGeometry.isBufferGeometry !== true ) {
 
-				// convert the geometry to bufferGeometry if it isn't already
-				var bufferGeometry = g;
+						throw new Error( 'THREE.ColladaExporter: Geometry is not of type THREE.BufferGeometry.' );
 
-				if ( bufferGeometry.isBufferGeometry !== true ) {
+					}
 
-					throw new Error( 'THREE.ColladaExporter: Geometry is not of type THREE.BufferGeometry.' );
+					var meshid = `Mesh${libraryGeometries.length + 1}`;
+					var indexCount = bufferGeometry.index ? bufferGeometry.index.count * bufferGeometry.index.itemSize : bufferGeometry.attributes.position.count;
+					var groups = bufferGeometry.groups != null && bufferGeometry.groups.length !== 0 ? bufferGeometry.groups : [ {
+						start: 0,
+						count: indexCount,
+						materialIndex: 0
+					} ];
+					var gname = g.name ? ` name="${g.name}"` : '';
+					var gnode = `<geometry id="${meshid}"${gname}><mesh>`; // define the geometry node and the vertices for the geometry
 
-				}
+					var posName = `${meshid}-position`;
+					var vertName = `${meshid}-vertices`;
+					gnode += getAttribute( bufferGeometry.attributes.position, posName, [ 'X', 'Y', 'Z' ], 'float' );
+					gnode += `<vertices id="${vertName}"><input semantic="POSITION" source="#${posName}" /></vertices>`; // NOTE: We're not optimizing the attribute arrays here, so they're all the same length and
+					// can therefore share the same triangle indices. However, MeshLab seems to have trouble opening
+					// models with attributes that share an offset.
+					// MeshLab Bug#424: https://sourceforge.net/p/meshlab/bugs/424/
+					// serialize normals
 
-				var meshid = `Mesh${ libraryGeometries.length + 1 }`;
+					var triangleInputs = `<input semantic="VERTEX" source="#${vertName}" offset="0" />`;
 
-				var indexCount =
-					bufferGeometry.index ?
-						bufferGeometry.index.count * bufferGeometry.index.itemSize :
-						bufferGeometry.attributes.position.count;
+					if ( 'normal' in bufferGeometry.attributes ) {
 
-				var groups =
-					bufferGeometry.groups != null && bufferGeometry.groups.length !== 0 ?
-						bufferGeometry.groups :
-						[ { start: 0, count: indexCount, materialIndex: 0 } ];
+						var normName = `${meshid}-normal`;
+						gnode += getAttribute( bufferGeometry.attributes.normal, normName, [ 'X', 'Y', 'Z' ], 'float' );
+						triangleInputs += `<input semantic="NORMAL" source="#${normName}" offset="0" />`;
 
+					} // serialize uvs
 
-				var gname = g.name ? ` name="${ g.name }"` : '';
-				var gnode = `<geometry id="${ meshid }"${ gname }><mesh>`;
 
-				// define the geometry node and the vertices for the geometry
-				var posName = `${ meshid }-position`;
-				var vertName = `${ meshid }-vertices`;
-				gnode += getAttribute( bufferGeometry.attributes.position, posName, [ 'X', 'Y', 'Z' ], 'float' );
-				gnode += `<vertices id="${ vertName }"><input semantic="POSITION" source="#${ posName }" /></vertices>`;
+					if ( 'uv' in bufferGeometry.attributes ) {
 
-				// NOTE: We're not optimizing the attribute arrays here, so they're all the same length and
-				// can therefore share the same triangle indices. However, MeshLab seems to have trouble opening
-				// models with attributes that share an offset.
-				// MeshLab Bug#424: https://sourceforge.net/p/meshlab/bugs/424/
+						var uvName = `${meshid}-texcoord`;
+						gnode += getAttribute( bufferGeometry.attributes.uv, uvName, [ 'S', 'T' ], 'float' );
+						triangleInputs += `<input semantic="TEXCOORD" source="#${uvName}" offset="0" set="0" />`;
 
-				// serialize normals
-				var triangleInputs = `<input semantic="VERTEX" source="#${ vertName }" offset="0" />`;
-				if ( 'normal' in bufferGeometry.attributes ) {
+					} // serialize lightmap uvs
 
-					var normName = `${ meshid }-normal`;
-					gnode += getAttribute( bufferGeometry.attributes.normal, normName, [ 'X', 'Y', 'Z' ], 'float' );
-					triangleInputs += `<input semantic="NORMAL" source="#${ normName }" offset="0" />`;
 
-				}
+					if ( 'uv2' in bufferGeometry.attributes ) {
 
-				// serialize uvs
-				if ( 'uv' in bufferGeometry.attributes ) {
+						var uvName = `${meshid}-texcoord2`;
+						gnode += getAttribute( bufferGeometry.attributes.uv2, uvName, [ 'S', 'T' ], 'float' );
+						triangleInputs += `<input semantic="TEXCOORD" source="#${uvName}" offset="0" set="1" />`;
 
-					var uvName = `${ meshid }-texcoord`;
-					gnode += getAttribute( bufferGeometry.attributes.uv, uvName, [ 'S', 'T' ], 'float' );
-					triangleInputs += `<input semantic="TEXCOORD" source="#${ uvName }" offset="0" set="0" />`;
+					} // serialize colors
 
-				}
 
-				// serialize lightmap uvs
-				if ( 'uv2' in bufferGeometry.attributes ) {
+					if ( 'color' in bufferGeometry.attributes ) {
 
-					var uvName = `${ meshid }-texcoord2`;
-					gnode += getAttribute( bufferGeometry.attributes.uv2, uvName, [ 'S', 'T' ], 'float' );
-					triangleInputs += `<input semantic="TEXCOORD" source="#${ uvName }" offset="0" set="1" />`;
+						var colName = `${meshid}-color`;
+						gnode += getAttribute( bufferGeometry.attributes.color, colName, [ 'X', 'Y', 'Z' ], 'uint8' );
+						triangleInputs += `<input semantic="COLOR" source="#${colName}" offset="0" />`;
 
-				}
+					}
 
-				// serialize colors
-				if ( 'color' in bufferGeometry.attributes ) {
+					var indexArray = null;
 
-					var colName = `${ meshid }-color`;
-					gnode += getAttribute( bufferGeometry.attributes.color, colName, [ 'X', 'Y', 'Z' ], 'uint8' );
-					triangleInputs += `<input semantic="COLOR" source="#${ colName }" offset="0" />`;
+					if ( bufferGeometry.index ) {
 
-				}
+						indexArray = attrBufferToArray( bufferGeometry.index );
 
-				var indexArray = null;
-				if ( bufferGeometry.index ) {
+					} else {
 
-					indexArray = attrBufferToArray( bufferGeometry.index );
+						indexArray = new Array( indexCount );
 
-				} else {
+						for ( var i = 0, l = indexArray.length; i < l; i ++ ) indexArray[ i ] = i;
 
-					indexArray = new Array( indexCount );
-					for ( var i = 0, l = indexArray.length; i < l; i ++ ) indexArray[ i ] = i;
+					}
 
-				}
+					for ( var i = 0, l = groups.length; i < l; i ++ ) {
 
-				for ( var i = 0, l = groups.length; i < l; i ++ ) {
+						var group = groups[ i ];
+						var subarr = subArray( indexArray, group.start, group.count );
+						var polycount = subarr.length / 3;
+						gnode += `<triangles material="MESH_MATERIAL_${group.materialIndex}" count="${polycount}">`;
+						gnode += triangleInputs;
+						gnode += `<p>${subarr.join( ' ' )}</p>`;
+						gnode += '</triangles>';
 
-					var group = groups[ i ];
-					var subarr = subArray( indexArray, group.start, group.count );
-					var polycount = subarr.length / 3;
-					gnode += `<triangles material="MESH_MATERIAL_${ group.materialIndex }" count="${ polycount }">`;
-					gnode += triangleInputs;
+					}
 
-					gnode += `<p>${ subarr.join( ' ' ) }</p>`;
-					gnode += '</triangles>';
+					gnode += '</mesh></geometry>';
+					libraryGeometries.push( gnode );
+					info = {
+						meshid: meshid,
+						bufferGeometry: bufferGeometry
+					};
+					geometryInfo.set( g, info );
 
 				}
 
-				gnode += '</mesh></geometry>';
+				return info;
 
-				libraryGeometries.push( gnode );
+			} // Process the given texture into the image library
+			// Returns the image library
 
-				info = { meshid: meshid, bufferGeometry: bufferGeometry };
-				geometryInfo.set( g, info );
 
-			}
-
-			return info;
+			function processTexture( tex ) {
 
-		}
+				var texid = imageMap.get( tex );
 
-		// Process the given texture into the image library
-		// Returns the image library
-		function processTexture( tex ) {
+				if ( texid == null ) {
 
-			var texid = imageMap.get( tex );
-			if ( texid == null ) {
+					texid = `image-${libraryImages.length + 1}`;
+					var ext = 'png';
+					var name = tex.name || texid;
+					var imageNode = `<image id="${texid}" name="${name}">`;
 
-				texid = `image-${ libraryImages.length + 1 }`;
+					if ( version === '1.5.0' ) {
 
-				var ext = 'png';
-				var name = tex.name || texid;
-				var imageNode = `<image id="${ texid }" name="${ name }">`;
+						imageNode += `<init_from><ref>${options.textureDirectory}${name}.${ext}</ref></init_from>`;
 
-				if ( version === '1.5.0' ) {
+					} else {
 
-					imageNode += `<init_from><ref>${ options.textureDirectory }${ name }.${ ext }</ref></init_from>`;
+						// version image node 1.4.1
+						imageNode += `<init_from>${options.textureDirectory}${name}.${ext}</init_from>`;
 
-				} else {
+					}
 
-					// version image node 1.4.1
-					imageNode += `<init_from>${ options.textureDirectory }${ name }.${ ext }</init_from>`;
+					imageNode += '</image>';
+					libraryImages.push( imageNode );
+					imageMap.set( tex, texid );
+					textures.push( {
+						directory: options.textureDirectory,
+						name,
+						ext,
+						data: imageToData( tex.image, ext ),
+						original: tex
+					} );
 
 				}
 
-				imageNode += '</image>';
-
-				libraryImages.push( imageNode );
-				imageMap.set( tex, texid );
-				textures.push( {
-					directory: options.textureDirectory,
-					name,
-					ext,
-					data: imageToData( tex.image, ext ),
-					original: tex
-				} );
+				return texid;
 
-			}
+			} // Process the given material into the material and effect libraries
+			// Returns the material id
 
-			return texid;
-
-		}
 
-		// Process the given material into the material and effect libraries
-		// Returns the material id
-		function processMaterial( m ) {
+			function processMaterial( m ) {
 
-			var matid = materialMap.get( m );
+				var matid = materialMap.get( m );
 
-			if ( matid == null ) {
+				if ( matid == null ) {
 
-				matid = `Mat${ libraryEffects.length + 1 }`;
+					matid = `Mat${libraryEffects.length + 1}`;
+					var type = 'phong';
 
-				var type = 'phong';
+					if ( m.isMeshLambertMaterial === true ) {
 
-				if ( m.isMeshLambertMaterial === true ) {
+						type = 'lambert';
 
-					type = 'lambert';
+					} else if ( m.isMeshBasicMaterial === true ) {
 
-				} else if ( m.isMeshBasicMaterial === true ) {
+						type = 'constant';
 
-					type = 'constant';
+						if ( m.map !== null ) {
 
-					if ( m.map !== null ) {
+							// The Collada spec does not support diffuse texture maps with the
+							// constant shader type.
+							// mrdoob/three.js#15469
+							console.warn( 'ColladaExporter: Texture maps not supported with THREE.MeshBasicMaterial.' );
 
-						// The Collada spec does not support diffuse texture maps with the
-						// constant shader type.
-						// mrdoob/three.js#15469
-						console.warn( 'ColladaExporter: Texture maps not supported with MeshBasicMaterial.' );
+						}
 
 					}
 
-				}
+					var emissive = m.emissive ? m.emissive : new THREE.Color( 0, 0, 0 );
+					var diffuse = m.color ? m.color : new THREE.Color( 0, 0, 0 );
+					var specular = m.specular ? m.specular : new THREE.Color( 1, 1, 1 );
+					var shininess = m.shininess || 0;
+					var reflectivity = m.reflectivity || 0; // Do not export and alpha map for the reasons mentioned in issue (#13792)
+					// in three.js alpha maps are black and white, but collada expects the alpha
+					// channel to specify the transparency
+
+					var transparencyNode = '';
 
-				var emissive = m.emissive ? m.emissive : new THREE.Color( 0, 0, 0 );
-				var diffuse = m.color ? m.color : new THREE.Color( 0, 0, 0 );
-				var specular = m.specular ? m.specular : new THREE.Color( 1, 1, 1 );
-				var shininess = m.shininess || 0;
-				var reflectivity = m.reflectivity || 0;
+					if ( m.transparent === true ) {
 
-				// Do not export and alpha map for the reasons mentioned in issue (#13792)
-				// in three.js alpha maps are black and white, but collada expects the alpha
-				// channel to specify the transparency
-				var transparencyNode = '';
-				if ( m.transparent === true ) {
+						transparencyNode += '<transparent>' + ( m.map ? '<texture texture="diffuse-sampler"></texture>' : '<float>1</float>' ) + '</transparent>';
 
-					transparencyNode +=
-						'<transparent>' +
-						(
-							m.map ?
-								'<texture texture="diffuse-sampler"></texture>' :
-								'<float>1</float>'
-						) +
-						'</transparent>';
+						if ( m.opacity < 1 ) {
 
-					if ( m.opacity < 1 ) {
+							transparencyNode += `<transparency><float>${m.opacity}</float></transparency>`;
 
-						transparencyNode += `<transparency><float>${ m.opacity }</float></transparency>`;
+						}
 
 					}
 
+					var techniqueNode = `<technique sid="common"><${type}>` + '<emission>' + ( m.emissiveMap ? '<texture texture="emissive-sampler" texcoord="TEXCOORD" />' : `<color sid="emission">${emissive.r} ${emissive.g} ${emissive.b} 1</color>` ) + '</emission>' + ( type !== 'constant' ? '<diffuse>' + ( m.map ? '<texture texture="diffuse-sampler" texcoord="TEXCOORD" />' : `<color sid="diffuse">${diffuse.r} ${diffuse.g} ${diffuse.b} 1</color>` ) + '</diffuse>' : '' ) + ( type !== 'constant' ? '<bump>' + ( m.normalMap ? '<texture texture="bump-sampler" texcoord="TEXCOORD" />' : '' ) + '</bump>' : '' ) + ( type === 'phong' ? `<specular><color sid="specular">${specular.r} ${specular.g} ${specular.b} 1</color></specular>` + '<shininess>' + ( m.specularMap ? '<texture texture="specular-sampler" texcoord="TEXCOORD" />' : `<float sid="shininess">${shininess}</float>` ) + '</shininess>' : '' ) + `<reflective><color>${diffuse.r} ${diffuse.g} ${diffuse.b} 1</color></reflective>` + `<reflectivity><float>${reflectivity}</float></reflectivity>` + transparencyNode + `</${type}></technique>`;
+					var effectnode = `<effect id="${matid}-effect">` + '<profile_COMMON>' + ( m.map ? '<newparam sid="diffuse-surface"><surface type="2D">' + `<init_from>${processTexture( m.map )}</init_from>` + '</surface></newparam>' + '<newparam sid="diffuse-sampler"><sampler2D><source>diffuse-surface</source></sampler2D></newparam>' : '' ) + ( m.specularMap ? '<newparam sid="specular-surface"><surface type="2D">' + `<init_from>${processTexture( m.specularMap )}</init_from>` + '</surface></newparam>' + '<newparam sid="specular-sampler"><sampler2D><source>specular-surface</source></sampler2D></newparam>' : '' ) + ( m.emissiveMap ? '<newparam sid="emissive-surface"><surface type="2D">' + `<init_from>${processTexture( m.emissiveMap )}</init_from>` + '</surface></newparam>' + '<newparam sid="emissive-sampler"><sampler2D><source>emissive-surface</source></sampler2D></newparam>' : '' ) + ( m.normalMap ? '<newparam sid="bump-surface"><surface type="2D">' + `<init_from>${processTexture( m.normalMap )}</init_from>` + '</surface></newparam>' + '<newparam sid="bump-sampler"><sampler2D><source>bump-surface</source></sampler2D></newparam>' : '' ) + techniqueNode + ( m.side === THREE.DoubleSide ? '<extra><technique profile="THREEJS"><double_sided sid="double_sided" type="int">1</double_sided></technique></extra>' : '' ) + '</profile_COMMON>' + '</effect>';
+					var materialName = m.name ? ` name="${m.name}"` : '';
+					var materialNode = `<material id="${matid}"${materialName}><instance_effect url="#${matid}-effect" /></material>`;
+					libraryMaterials.push( materialNode );
+					libraryEffects.push( effectnode );
+					materialMap.set( m, matid );
+
 				}
 
-				var techniqueNode = `<technique sid="common"><${ type }>` +
-
-					'<emission>' +
-
-					(
-						m.emissiveMap ?
-							'<texture texture="emissive-sampler" texcoord="TEXCOORD" />' :
-							`<color sid="emission">${ emissive.r } ${ emissive.g } ${ emissive.b } 1</color>`
-					) +
-
-					'</emission>' +
-
-					(
-						type !== 'constant' ?
-							'<diffuse>' +
-
-						(
-							m.map ?
-								'<texture texture="diffuse-sampler" texcoord="TEXCOORD" />' :
-								`<color sid="diffuse">${ diffuse.r } ${ diffuse.g } ${ diffuse.b } 1</color>`
-						) +
-						'</diffuse>'
-							: ''
-					) +
-
-					(
-						type !== 'constant' ?
-							'<bump>' +
-
-						(
-							m.normalMap ? '<texture texture="bump-sampler" texcoord="TEXCOORD" />' : ''
-						) +
-						'</bump>'
-							: ''
-					) +
-
-					(
-						type === 'phong' ?
-							`<specular><color sid="specular">${ specular.r } ${ specular.g } ${ specular.b } 1</color></specular>` +
-
-						'<shininess>' +
-
-						(
-							m.specularMap ?
-								'<texture texture="specular-sampler" texcoord="TEXCOORD" />' :
-								`<float sid="shininess">${ shininess }</float>`
-						) +
-
-						'</shininess>'
-							: ''
-					) +
-
-					`<reflective><color>${ diffuse.r } ${ diffuse.g } ${ diffuse.b } 1</color></reflective>` +
-
-					`<reflectivity><float>${ reflectivity }</float></reflectivity>` +
-
-					transparencyNode +
-
-					`</${ type }></technique>`;
-
-				var effectnode =
-					`<effect id="${ matid }-effect">` +
-					'<profile_COMMON>' +
-
-					(
-						m.map ?
-							'<newparam sid="diffuse-surface"><surface type="2D">' +
-							`<init_from>${ processTexture( m.map ) }</init_from>` +
-							'</surface></newparam>' +
-							'<newparam sid="diffuse-sampler"><sampler2D><source>diffuse-surface</source></sampler2D></newparam>' :
-							''
-					) +
-
-					(
-						m.specularMap ?
-							'<newparam sid="specular-surface"><surface type="2D">' +
-							`<init_from>${ processTexture( m.specularMap ) }</init_from>` +
-							'</surface></newparam>' +
-							'<newparam sid="specular-sampler"><sampler2D><source>specular-surface</source></sampler2D></newparam>' :
-							''
-					) +
-
-					(
-						m.emissiveMap ?
-							'<newparam sid="emissive-surface"><surface type="2D">' +
-							`<init_from>${ processTexture( m.emissiveMap ) }</init_from>` +
-							'</surface></newparam>' +
-							'<newparam sid="emissive-sampler"><sampler2D><source>emissive-surface</source></sampler2D></newparam>' :
-							''
-					) +
-
-					(
-						m.normalMap ?
-							'<newparam sid="bump-surface"><surface type="2D">' +
-							`<init_from>${ processTexture( m.normalMap ) }</init_from>` +
-							'</surface></newparam>' +
-							'<newparam sid="bump-sampler"><sampler2D><source>bump-surface</source></sampler2D></newparam>' :
-							''
-					) +
-
-					techniqueNode +
-
-					(
-						m.side === THREE.DoubleSide ?
-							'<extra><technique profile="THREEJS"><double_sided sid="double_sided" type="int">1</double_sided></technique></extra>' :
-							''
-					) +
-
-					'</profile_COMMON>' +
-
-					'</effect>';
-
-				var materialName = m.name ? ` name="${ m.name }"` : '';
-				var materialNode = `<material id="${ matid }"${ materialName }><instance_effect url="#${ matid }-effect" /></material>`;
-
-				libraryMaterials.push( materialNode );
-				libraryEffects.push( effectnode );
-				materialMap.set( m, matid );
+				return matid;
 
-			}
+			} // Recursively process the object into a scene
 
-			return matid;
 
-		}
+			function processObject( o ) {
 
-		// Recursively process the object into a scene
-		function processObject( o ) {
+				var node = `<node name="${o.name}">`;
+				node += getTransform( o );
 
-			var node = `<node name="${ o.name }">`;
+				if ( o.isMesh === true && o.geometry !== null ) {
 
-			node += getTransform( o );
+					// function returns the id associated with the mesh and a "BufferGeometry" version
+					// of the geometry in case it's not a geometry.
+					var geomInfo = processGeometry( o.geometry );
+					var meshid = geomInfo.meshid;
+					var geometry = geomInfo.bufferGeometry; // ids of the materials to bind to the geometry
 
-			if ( o.isMesh === true && o.geometry !== null ) {
+					var matids = null;
+					var matidsArray = []; // get a list of materials to bind to the sub groups of the geometry.
+					// If the amount of subgroups is greater than the materials, than reuse
+					// the materials.
 
-				// function returns the id associated with the mesh and a "BufferGeometry" version
-				// of the geometry in case it's not a geometry.
-				var geomInfo = processGeometry( o.geometry );
-				var meshid = geomInfo.meshid;
-				var geometry = geomInfo.bufferGeometry;
+					var mat = o.material || new THREE.MeshBasicMaterial();
+					var materials = Array.isArray( mat ) ? mat : [ mat ];
 
-				// ids of the materials to bind to the geometry
-				var matids = null;
-				var matidsArray = [];
+					if ( geometry.groups.length > materials.length ) {
 
-				// get a list of materials to bind to the sub groups of the geometry.
-				// If the amount of subgroups is greater than the materials, than reuse
-				// the materials.
-				var mat = o.material || new THREE.MeshBasicMaterial();
-				var materials = Array.isArray( mat ) ? mat : [ mat ];
+						matidsArray = new Array( geometry.groups.length );
 
-				if ( geometry.groups.length > materials.length ) {
+					} else {
 
-					matidsArray = new Array( geometry.groups.length );
+						matidsArray = new Array( materials.length );
 
-				} else {
+					}
 
-					matidsArray = new Array( materials.length );
+					matids = matidsArray.fill().map( ( v, i ) => processMaterial( materials[ i % materials.length ] ) );
+					node += `<instance_geometry url="#${meshid}">` + ( matids != null ? '<bind_material><technique_common>' + matids.map( ( id, i ) => `<instance_material symbol="MESH_MATERIAL_${i}" target="#${id}" >` + '<bind_vertex_input semantic="TEXCOORD" input_semantic="TEXCOORD" input_set="0" />' + '</instance_material>' ).join( '' ) + '</technique_common></bind_material>' : '' ) + '</instance_geometry>';
 
 				}
 
-				matids = matidsArray.fill().map( ( v, i ) => processMaterial( materials[ i % materials.length ] ) );
-
-				node +=
-					`<instance_geometry url="#${ meshid }">` +
-
-					(
-						matids != null ?
-							'<bind_material><technique_common>' +
-							matids.map( ( id, i ) =>
-
-								`<instance_material symbol="MESH_MATERIAL_${ i }" target="#${ id }" >` +
-
-								'<bind_vertex_input semantic="TEXCOORD" input_semantic="TEXCOORD" input_set="0" />' +
-
-								'</instance_material>'
-							).join( '' ) +
-							'</technique_common></bind_material>' :
-							''
-					) +
-
-					'</instance_geometry>';
+				o.children.forEach( c => node += processObject( c ) );
+				node += '</node>';
+				return node;
 
 			}
 
-			o.children.forEach( c => node += processObject( c ) );
+			var geometryInfo = new WeakMap();
+			var materialMap = new WeakMap();
+			var imageMap = new WeakMap();
+			var textures = [];
+			var libraryImages = [];
+			var libraryGeometries = [];
+			var libraryEffects = [];
+			var libraryMaterials = [];
+			var libraryVisualScenes = processObject( object );
+			var specLink = version === '1.4.1' ? 'http://www.collada.org/2005/11/COLLADASchema' : 'https://www.khronos.org/collada/';
+			var dae = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>' + `<COLLADA xmlns="${specLink}" version="${version}">` + '<asset>' + ( '<contributor>' + '<authoring_tool>three.js Collada Exporter</authoring_tool>' + ( options.author !== null ? `<author>${options.author}</author>` : '' ) + '</contributor>' + `<created>${new Date().toISOString()}</created>` + `<modified>${new Date().toISOString()}</modified>` + '<up_axis>Y_UP</up_axis>' ) + '</asset>';
+			dae += `<library_images>${libraryImages.join( '' )}</library_images>`;
+			dae += `<library_effects>${libraryEffects.join( '' )}</library_effects>`;
+			dae += `<library_materials>${libraryMaterials.join( '' )}</library_materials>`;
+			dae += `<library_geometries>${libraryGeometries.join( '' )}</library_geometries>`;
+			dae += `<library_visual_scenes><visual_scene id="Scene" name="scene">${libraryVisualScenes}</visual_scene></library_visual_scenes>`;
+			dae += '<scene><instance_visual_scene url="#Scene"/></scene>';
+			dae += '</COLLADA>';
+			var res = {
+				data: format( dae ),
+				textures
+			};
+
+			if ( typeof onDone === 'function' ) {
+
+				requestAnimationFrame( () => onDone( res ) );
 
-			node += '</node>';
-
-			return node;
-
-		}
-
-		var geometryInfo = new WeakMap();
-		var materialMap = new WeakMap();
-		var imageMap = new WeakMap();
-		var textures = [];
-
-		var libraryImages = [];
-		var libraryGeometries = [];
-		var libraryEffects = [];
-		var libraryMaterials = [];
-		var libraryVisualScenes = processObject( object );
-
-		var specLink = version === '1.4.1' ? 'http://www.collada.org/2005/11/COLLADASchema' : 'https://www.khronos.org/collada/';
-		var dae =
-			'<?xml version="1.0" encoding="UTF-8" standalone="no" ?>' +
-			`<COLLADA xmlns="${ specLink }" version="${ version }">` +
-			'<asset>' +
-			(
-				'<contributor>' +
-				'<authoring_tool>three.js Collada Exporter</authoring_tool>' +
-				( options.author !== null ? `<author>${ options.author }</author>` : '' ) +
-				'</contributor>' +
-				`<created>${ ( new Date() ).toISOString() }</created>` +
-				`<modified>${ ( new Date() ).toISOString() }</modified>` +
-				'<up_axis>Y_UP</up_axis>'
-			) +
-			'</asset>';
-
-		dae += `<library_images>${ libraryImages.join( '' ) }</library_images>`;
-
-		dae += `<library_effects>${ libraryEffects.join( '' ) }</library_effects>`;
-
-		dae += `<library_materials>${ libraryMaterials.join( '' ) }</library_materials>`;
-
-		dae += `<library_geometries>${ libraryGeometries.join( '' ) }</library_geometries>`;
-
-		dae += `<library_visual_scenes><visual_scene id="Scene" name="scene">${ libraryVisualScenes }</visual_scene></library_visual_scenes>`;
-
-		dae += '<scene><instance_visual_scene url="#Scene"/></scene>';
-
-		dae += '</COLLADA>';
-
-		var res = {
-			data: format( dae ),
-			textures
-		};
-
-		if ( typeof onDone === 'function' ) {
+			}
 
-			requestAnimationFrame( () => onDone( res ) );
+			return res;
 
 		}
+	};
 
-		return res;
-
-	}
+	THREE.ColladaExporter = ColladaExporter;
 
-};
+} )();

+ 126 - 144
examples/js/exporters/DRACOExporter.js

@@ -1,248 +1,230 @@
-/**
+( function () {
+
+	/**
  * Export draco compressed files from threejs geometry objects.
  *
  * Draco files are compressed and usually are smaller than conventional 3D file formats.
  *
  * The exporter receives a options object containing
- *  - decodeSpeed, indicates how to tune the encoder regarding decode speed (0 gives better speed but worst quality)
- *  - encodeSpeed, indicates how to tune the encoder parameters (0 gives better speed but worst quality)
- *  - encoderMethod
- *  - quantization, indicates the presision of each type of data stored in the draco file in the order (POSITION, NORMAL, COLOR, TEX_COORD, GENERIC)
- *  - exportUvs
- *  - exportNormals
+ *	- decodeSpeed, indicates how to tune the encoder regarding decode speed (0 gives better speed but worst quality)
+ *	- encodeSpeed, indicates how to tune the encoder parameters (0 gives better speed but worst quality)
+ *	- encoderMethod
+ *	- quantization, indicates the presision of each type of data stored in the draco file in the order (POSITION, NORMAL, COLOR, TEX_COORD, GENERIC)
+ *	- exportUvs
+ *	- exportNormals
  */
 
-/* global DracoEncoderModule */
-
-THREE.DRACOExporter = function () {};
-
-THREE.DRACOExporter.prototype = {
-
-	constructor: THREE.DRACOExporter,
-
-	parse: function ( object, options ) {
+	/* global DracoEncoderModule */
+	var DRACOExporter = function () {};
 
-		if ( object.isBufferGeometry === true ) {
+	DRACOExporter.prototype = {
+		constructor: DRACOExporter,
+		parse: function ( object, options ) {
 
-			throw new Error( 'DRACOExporter: The first parameter of parse() is now an instance of Mesh or Points.' );
+			if ( object.isBufferGeometry === true ) {
 
-		}
-
-		if ( DracoEncoderModule === undefined ) {
-
-			throw new Error( 'THREE.DRACOExporter: required the draco_decoder to work.' );
-
-		}
+				throw new Error( 'DRACOExporter: The first parameter of parse() is now an instance of Mesh or Points.' );
 
-		if ( options === undefined ) {
+			}
 
-			options = {
+			if ( DracoEncoderModule === undefined ) {
 
-				decodeSpeed: 5,
-				encodeSpeed: 5,
-				encoderMethod: THREE.DRACOExporter.MESH_EDGEBREAKER_ENCODING,
-				quantization: [ 16, 8, 8, 8, 8 ],
-				exportUvs: true,
-				exportNormals: true,
-				exportColor: false,
+				throw new Error( 'THREE.DRACOExporter: required the draco_decoder to work.' );
 
-			};
+			}
 
-		}
+			if ( options === undefined ) {
 
-		var geometry = object.geometry;
+				options = {
+					decodeSpeed: 5,
+					encodeSpeed: 5,
+					encoderMethod: DRACOExporter.MESH_EDGEBREAKER_ENCODING,
+					quantization: [ 16, 8, 8, 8, 8 ],
+					exportUvs: true,
+					exportNormals: true,
+					exportColor: false
+				};
 
-		var dracoEncoder = DracoEncoderModule();
-		var encoder = new dracoEncoder.Encoder();
-		var builder;
-		var dracoObject;
+			}
 
+			var geometry = object.geometry;
+			var dracoEncoder = DracoEncoderModule();
+			var encoder = new dracoEncoder.Encoder();
+			var builder;
+			var dracoObject;
 
-		if ( geometry.isBufferGeometry !== true ) {
+			if ( geometry.isBufferGeometry !== true ) {
 
-			throw new Error( 'THREE.DRACOExporter.parse(geometry, options): geometry is not a THREE.BufferGeometry instance.' );
+				throw new Error( 'THREE.DRACOExporter.parse(geometry, options): geometry is not a THREE.BufferGeometry instance.' );
 
-		}
+			}
 
-		if ( object.isMesh === true ) {
+			if ( object.isMesh === true ) {
 
-			builder = new dracoEncoder.MeshBuilder();
-			dracoObject = new dracoEncoder.Mesh();
+				builder = new dracoEncoder.MeshBuilder();
+				dracoObject = new dracoEncoder.Mesh();
+				var vertices = geometry.getAttribute( 'position' );
+				builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array );
+				var faces = geometry.getIndex();
 
-			var vertices = geometry.getAttribute( 'position' );
-			builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array );
+				if ( faces !== null ) {
 
-			var faces = geometry.getIndex();
+					builder.AddFacesToMesh( dracoObject, faces.count / 3, faces.array );
 
-			if ( faces !== null ) {
+				} else {
 
-				builder.AddFacesToMesh( dracoObject, faces.count / 3, faces.array );
+					var faces = new ( vertices.count > 65535 ? Uint32Array : Uint16Array )( vertices.count );
 
-			} else {
+					for ( var i = 0; i < faces.length; i ++ ) {
 
-				var faces = new ( vertices.count > 65535 ? Uint32Array : Uint16Array )( vertices.count );
+						faces[ i ] = i;
 
-				for ( var i = 0; i < faces.length; i ++ ) {
+					}
 
-					faces[ i ] = i;
+					builder.AddFacesToMesh( dracoObject, vertices.count, faces );
 
 				}
 
-				builder.AddFacesToMesh( dracoObject, vertices.count, faces );
+				if ( options.exportNormals === true ) {
 
-			}
+					var normals = geometry.getAttribute( 'normal' );
 
-			if ( options.exportNormals === true ) {
+					if ( normals !== undefined ) {
 
-				var normals = geometry.getAttribute( 'normal' );
+						builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.NORMAL, normals.count, normals.itemSize, normals.array );
 
-				if ( normals !== undefined ) {
-
-					builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.NORMAL, normals.count, normals.itemSize, normals.array );
+					}
 
 				}
 
-			}
+				if ( options.exportUvs === true ) {
 
-			if ( options.exportUvs === true ) {
+					var uvs = geometry.getAttribute( 'uv' );
 
-				var uvs = geometry.getAttribute( 'uv' );
+					if ( uvs !== undefined ) {
 
-				if ( uvs !== undefined ) {
+						builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.TEX_COORD, uvs.count, uvs.itemSize, uvs.array );
 
-					builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.TEX_COORD, uvs.count, uvs.itemSize, uvs.array );
+					}
 
 				}
 
-			}
+				if ( options.exportColor === true ) {
 
-			if ( options.exportColor === true ) {
+					var colors = geometry.getAttribute( 'color' );
 
-				var colors = geometry.getAttribute( 'color' );
+					if ( colors !== undefined ) {
 
-				if ( colors !== undefined ) {
+						builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, colors.array );
 
-					builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, colors.array );
+					}
 
 				}
 
-			}
-
-		} else if ( object.isPoints === true ) {
+			} else if ( object.isPoints === true ) {
 
-			builder = new dracoEncoder.PointCloudBuilder();
-			dracoObject = new dracoEncoder.PointCloud();
+				builder = new dracoEncoder.PointCloudBuilder();
+				dracoObject = new dracoEncoder.PointCloud();
+				var vertices = geometry.getAttribute( 'position' );
+				builder.AddFloatAttribute( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array );
 
-			var vertices = geometry.getAttribute( 'position' );
-			builder.AddFloatAttribute( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array );
+				if ( options.exportColor === true ) {
 
-			if ( options.exportColor === true ) {
+					var colors = geometry.getAttribute( 'color' );
 
-				var colors = geometry.getAttribute( 'color' );
+					if ( colors !== undefined ) {
 
-				if ( colors !== undefined ) {
+						builder.AddFloatAttribute( dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, colors.array );
 
-					builder.AddFloatAttribute( dracoObject, dracoEncoder.COLOR, colors.count, colors.itemSize, colors.array );
+					}
 
 				}
 
-			}
-
-		} else {
+			} else {
 
-			throw new Error( 'DRACOExporter: Unsupported object type.' );
+				throw new Error( 'DRACOExporter: Unsupported object type.' );
 
-		}
+			} //Compress using draco encoder
 
-		//Compress using draco encoder
 
-		var encodedData = new dracoEncoder.DracoInt8Array();
+			var encodedData = new dracoEncoder.DracoInt8Array(); //Sets the desired encoding and decoding speed for the given options from 0 (slowest speed, but the best compression) to 10 (fastest, but the worst compression).
 
-		//Sets the desired encoding and decoding speed for the given options from 0 (slowest speed, but the best compression) to 10 (fastest, but the worst compression).
+			var encodeSpeed = options.encodeSpeed !== undefined ? options.encodeSpeed : 5;
+			var decodeSpeed = options.decodeSpeed !== undefined ? options.decodeSpeed : 5;
+			encoder.SetSpeedOptions( encodeSpeed, decodeSpeed ); // Sets the desired encoding method for a given geometry.
 
-		var encodeSpeed = ( options.encodeSpeed !== undefined ) ? options.encodeSpeed : 5;
-		var decodeSpeed = ( options.decodeSpeed !== undefined ) ? options.decodeSpeed : 5;
+			if ( options.encoderMethod !== undefined ) {
 
-		encoder.SetSpeedOptions( encodeSpeed, decodeSpeed );
+				encoder.SetEncodingMethod( options.encoderMethod );
 
-		// Sets the desired encoding method for a given geometry.
+			} // Sets the quantization (number of bits used to represent) compression options for a named attribute.
+			// The attribute values will be quantized in a box defined by the maximum extent of the attribute values.
 
-		if ( options.encoderMethod !== undefined ) {
 
-			encoder.SetEncodingMethod( options.encoderMethod );
+			if ( options.quantization !== undefined ) {
 
-		}
+				for ( var i = 0; i < 5; i ++ ) {
 
-		// Sets the quantization (number of bits used to represent) compression options for a named attribute.
-		// The attribute values will be quantized in a box defined by the maximum extent of the attribute values.
-		if ( options.quantization !== undefined ) {
+					if ( options.quantization[ i ] !== undefined ) {
 
-			for ( var i = 0; i < 5; i ++ ) {
+						encoder.SetAttributeQuantization( i, options.quantization[ i ] );
 
-				if ( options.quantization[ i ] !== undefined ) {
-
-					encoder.SetAttributeQuantization( i, options.quantization[ i ] );
+					}
 
 				}
 
 			}
 
-		}
-
-		var length;
-
-		if ( object.isMesh === true ) {
-
-			length = encoder.EncodeMeshToDracoBuffer( dracoObject, encodedData );
+			var length;
 
-		} else {
+			if ( object.isMesh === true ) {
 
-			length = encoder.EncodePointCloudToDracoBuffer( dracoObject, true, encodedData );
+				length = encoder.EncodeMeshToDracoBuffer( dracoObject, encodedData );
 
-		}
+			} else {
 
-		dracoEncoder.destroy( dracoObject );
+				length = encoder.EncodePointCloudToDracoBuffer( dracoObject, true, encodedData );
 
-		if ( length === 0 ) {
+			}
 
-			throw new Error( 'THREE.DRACOExporter: Draco encoding failed.' );
+			dracoEncoder.destroy( dracoObject );
 
-		}
+			if ( length === 0 ) {
 
-		//Copy encoded data to buffer.
-		var outputData = new Int8Array( new ArrayBuffer( length ) );
+				throw new Error( 'THREE.DRACOExporter: Draco encoding failed.' );
 
-		for ( var i = 0; i < length; i ++ ) {
+			} //Copy encoded data to buffer.
 
-			outputData[ i ] = encodedData.GetValue( i );
 
-		}
+			var outputData = new Int8Array( new ArrayBuffer( length ) );
 
-		dracoEncoder.destroy( encodedData );
-		dracoEncoder.destroy( encoder );
-		dracoEncoder.destroy( builder );
+			for ( var i = 0; i < length; i ++ ) {
 
-		return outputData;
+				outputData[ i ] = encodedData.GetValue( i );
 
-	}
+			}
 
-};
+			dracoEncoder.destroy( encodedData );
+			dracoEncoder.destroy( encoder );
+			dracoEncoder.destroy( builder );
+			return outputData;
 
-// Encoder methods
+		}
+	}; // Encoder methods
 
-THREE.DRACOExporter.MESH_EDGEBREAKER_ENCODING = 1;
-THREE.DRACOExporter.MESH_SEQUENTIAL_ENCODING = 0;
+	DRACOExporter.MESH_EDGEBREAKER_ENCODING = 1;
+	DRACOExporter.MESH_SEQUENTIAL_ENCODING = 0; // Geometry type
 
-// Geometry type
+	DRACOExporter.POINT_CLOUD = 0;
+	DRACOExporter.TRIANGULAR_MESH = 1; // Attribute type
 
-THREE.DRACOExporter.POINT_CLOUD = 0;
-THREE.DRACOExporter.TRIANGULAR_MESH = 1;
+	DRACOExporter.INVALID = - 1;
+	DRACOExporter.POSITION = 0;
+	DRACOExporter.NORMAL = 1;
+	DRACOExporter.COLOR = 2;
+	DRACOExporter.TEX_COORD = 3;
+	DRACOExporter.GENERIC = 4;
 
-// Attribute type
+	THREE.DRACOExporter = DRACOExporter;
 
-THREE.DRACOExporter.INVALID = - 1;
-THREE.DRACOExporter.POSITION = 0;
-THREE.DRACOExporter.NORMAL = 1;
-THREE.DRACOExporter.COLOR = 2;
-THREE.DRACOExporter.TEX_COORD = 3;
-THREE.DRACOExporter.GENERIC = 4;
+} )();

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 902 - 1026
examples/js/exporters/GLTFExporter.js


+ 112 - 126
examples/js/exporters/MMDExporter.js

@@ -1,208 +1,194 @@
-/**
+( function () {
+
+	/**
  * Dependencies
- *  - mmd-parser https://github.com/takahirox/mmd-parser
+ *	- mmd-parser https://github.com/takahirox/mmd-parser
  */
 
-THREE.MMDExporter = function () {
-
-	// Unicode to Shift_JIS table
-	var u2sTable;
+	var MMDExporter = function () {
 
-	function unicodeToShiftjis( str ) {
+		// Unicode to Shift_JIS table
+		var u2sTable;
 
-		if ( u2sTable === undefined ) {
+		function unicodeToShiftjis( str ) {
 
-			var encoder = new MMDParser.CharsetEncoder(); // eslint-disable-line no-undef
-			var table = encoder.s2uTable;
-			u2sTable = {};
+			if ( u2sTable === undefined ) {
 
-			var keys = Object.keys( table );
+				var encoder = new MMDParser.CharsetEncoder(); // eslint-disable-line no-undef
 
-			for ( var i = 0, il = keys.length; i < il; i ++ ) {
+				var table = encoder.s2uTable;
+				u2sTable = {};
+				var keys = Object.keys( table );
 
-				var key = keys[ i ];
+				for ( var i = 0, il = keys.length; i < il; i ++ ) {
 
-				var value = table[ key ];
-				key = parseInt( key );
+					var key = keys[ i ];
+					var value = table[ key ];
+					key = parseInt( key );
+					u2sTable[ value ] = key;
 
-				u2sTable[ value ] = key;
+				}
 
 			}
 
-		}
+			var array = [];
 
-		var array = [];
+			for ( var i = 0, il = str.length; i < il; i ++ ) {
 
-		for ( var i = 0, il = str.length; i < il; i ++ ) {
+				var code = str.charCodeAt( i );
+				var value = u2sTable[ code ];
 
-			var code = str.charCodeAt( i );
+				if ( value === undefined ) {
 
-			var value = u2sTable[ code ];
+					throw 'cannot convert charcode 0x' + code.toString( 16 );
 
-			if ( value === undefined ) {
+				} else if ( value > 0xff ) {
 
-				throw 'cannot convert charcode 0x' + code.toString( 16 );
+					array.push( value >> 8 & 0xff );
+					array.push( value & 0xff );
 
-			} else if ( value > 0xff ) {
+				} else {
 
-				array.push( ( value >> 8 ) & 0xff );
-				array.push( value & 0xff );
+					array.push( value & 0xff );
 
-			} else {
-
-				array.push( value & 0xff );
+				}
 
 			}
 
-		}
-
-		return new Uint8Array( array );
-
-	}
+			return new Uint8Array( array );
 
-	function getBindBones( skin ) {
+		}
 
-		// any more efficient ways?
-		var poseSkin = skin.clone();
-		poseSkin.pose();
-		return poseSkin.skeleton.bones;
+		function getBindBones( skin ) {
 
-	}
+			// any more efficient ways?
+			var poseSkin = skin.clone();
+			poseSkin.pose();
+			return poseSkin.skeleton.bones;
 
-	/* TODO: implement
+		}
+		/* TODO: implement
 	// mesh -> pmd
 	this.parsePmd = function ( object ) {
-
-	};
+		};
 	*/
 
-	/* TODO: implement
+		/* TODO: implement
 	// mesh -> pmx
 	this.parsePmx = function ( object ) {
-
-	};
+		};
 	*/
 
-	/*
+		/*
 	 * skeleton -> vpd
 	 * Returns Shift_JIS encoded Uint8Array. Otherwise return strings.
 	 */
-	this.parseVpd = function ( skin, outputShiftJis, useOriginalBones ) {
-
-		if ( skin.isSkinnedMesh !== true ) {
-
-			console.warn( 'THREE.MMDExporter: parseVpd() requires SkinnedMesh instance.' );
-			return null;
 
-		}
-
-		function toStringsFromNumber( num ) {
-
-			if ( Math.abs( num ) < 1e-6 ) num = 0;
 
-			var a = num.toString();
+		this.parseVpd = function ( skin, outputShiftJis, useOriginalBones ) {
 
-			if ( a.indexOf( '.' ) === - 1 ) {
+			if ( skin.isSkinnedMesh !== true ) {
 
-				a += '.';
+				console.warn( 'THREE.MMDExporter: parseVpd() requires SkinnedMesh instance.' );
+				return null;
 
 			}
 
-			a += '000000';
-
-			var index = a.indexOf( '.' );
-
-			var d = a.slice( 0, index );
-			var p = a.slice( index + 1, index + 7 );
+			function toStringsFromNumber( num ) {
 
-			return d + '.' + p;
+				if ( Math.abs( num ) < 1e-6 ) num = 0;
+				var a = num.toString();
 
-		}
-
-		function toStringsFromArray( array ) {
+				if ( a.indexOf( '.' ) === - 1 ) {
 
-			var a = [];
+					a += '.';
 
-			for ( var i = 0, il = array.length; i < il; i ++ ) {
+				}
 
-				a.push( toStringsFromNumber( array[ i ] ) );
+				a += '000000';
+				var index = a.indexOf( '.' );
+				var d = a.slice( 0, index );
+				var p = a.slice( index + 1, index + 7 );
+				return d + '.' + p;
 
 			}
 
-			return a.join( ',' );
+			function toStringsFromArray( array ) {
 
-		}
+				var a = [];
 
-		skin.updateMatrixWorld( true );
+				for ( var i = 0, il = array.length; i < il; i ++ ) {
 
-		var bones = skin.skeleton.bones;
-		var bones2 = getBindBones( skin );
+					a.push( toStringsFromNumber( array[ i ] ) );
 
-		var position = new THREE.Vector3();
-		var quaternion = new THREE.Quaternion();
-		var quaternion2 = new THREE.Quaternion();
-		var matrix = new THREE.Matrix4();
+				}
 
-		var array = [];
-		array.push( 'Vocaloid Pose Data file' );
-		array.push( '' );
-		array.push( ( skin.name !== '' ? skin.name.replace( /\s/g, '_' ) : 'skin' ) + '.osm;' );
-		array.push( bones.length + ';' );
-		array.push( '' );
+				return a.join( ',' );
+
+			}
 
-		for ( var i = 0, il = bones.length; i < il; i ++ ) {
+			skin.updateMatrixWorld( true );
+			var bones = skin.skeleton.bones;
+			var bones2 = getBindBones( skin );
+			var position = new THREE.Vector3();
+			var quaternion = new THREE.Quaternion();
+			var quaternion2 = new THREE.Quaternion();
+			var matrix = new THREE.Matrix4();
+			var array = [];
+			array.push( 'Vocaloid Pose Data file' );
+			array.push( '' );
+			array.push( ( skin.name !== '' ? skin.name.replace( /\s/g, '_' ) : 'skin' ) + '.osm;' );
+			array.push( bones.length + ';' );
+			array.push( '' );
 
-			var bone = bones[ i ];
-			var bone2 = bones2[ i ];
+			for ( var i = 0, il = bones.length; i < il; i ++ ) {
 
-			/*
+				var bone = bones[ i ];
+				var bone2 = bones2[ i ];
+				/*
 			 * use the bone matrix saved before solving IK.
 			 * see CCDIKSolver for the detail.
 			 */
-			if ( useOriginalBones === true &&
-				bone.userData.ik !== undefined &&
-				bone.userData.ik.originalMatrix !== undefined ) {
-
-				matrix.fromArray( bone.userData.ik.originalMatrix );
-
-			} else {
-
-				matrix.copy( bone.matrix );
 
-			}
+				if ( useOriginalBones === true && bone.userData.ik !== undefined && bone.userData.ik.originalMatrix !== undefined ) {
 
-			position.setFromMatrixPosition( matrix );
-			quaternion.setFromRotationMatrix( matrix );
+					matrix.fromArray( bone.userData.ik.originalMatrix );
 
-			var pArray = position.sub( bone2.position ).toArray();
-			var qArray = quaternion2.copy( bone2.quaternion ).conjugate().multiply( quaternion ).toArray();
+				} else {
 
-			// right to left
-			pArray[ 2 ] = - pArray[ 2 ];
-			qArray[ 0 ] = - qArray[ 0 ];
-			qArray[ 1 ] = - qArray[ 1 ];
+					matrix.copy( bone.matrix );
 
-			array.push( 'Bone' + i + '{' + bone.name );
-			array.push( '  ' + toStringsFromArray( pArray ) + ';' );
-			array.push( '  ' + toStringsFromArray( qArray ) + ';' );
-			array.push( '}' );
-			array.push( '' );
+				}
 
-		}
+				position.setFromMatrixPosition( matrix );
+				quaternion.setFromRotationMatrix( matrix );
+				var pArray = position.sub( bone2.position ).toArray();
+				var qArray = quaternion2.copy( bone2.quaternion ).conjugate().multiply( quaternion ).toArray(); // right to left
 
-		array.push( '' );
+				pArray[ 2 ] = - pArray[ 2 ];
+				qArray[ 0 ] = - qArray[ 0 ];
+				qArray[ 1 ] = - qArray[ 1 ];
+				array.push( 'Bone' + i + '{' + bone.name );
+				array.push( '	' + toStringsFromArray( pArray ) + ';' );
+				array.push( '	' + toStringsFromArray( qArray ) + ';' );
+				array.push( '}' );
+				array.push( '' );
 
-		var lines = array.join( '\n' );
-
-		return ( outputShiftJis === true ) ? unicodeToShiftjis( lines ) : lines;
+			}
 
-	};
+			array.push( '' );
+			var lines = array.join( '\n' );
+			return outputShiftJis === true ? unicodeToShiftjis( lines ) : lines;
 
-	/* TODO: implement
+		};
+		/* TODO: implement
 	// animation + skeleton -> vmd
 	this.parseVmd = function ( object ) {
+		};
+	*/
 
 	};
-	*/
 
-};
+	THREE.MMDExporter = MMDExporter;
+
+} )();

+ 154 - 174
examples/js/exporters/OBJExporter.js

@@ -1,304 +1,284 @@
-THREE.OBJExporter = function () {};
+( function () {
 
-THREE.OBJExporter.prototype = {
+	var OBJExporter = function () {};
 
-	constructor: THREE.OBJExporter,
+	OBJExporter.prototype = {
+		constructor: OBJExporter,
+		parse: function ( object ) {
 
-	parse: function ( object ) {
+			var output = '';
+			var indexVertex = 0;
+			var indexVertexUvs = 0;
+			var indexNormals = 0;
+			var vertex = new THREE.Vector3();
+			var color = new THREE.Color();
+			var normal = new THREE.Vector3();
+			var uv = new THREE.Vector2();
+			var i,
+				j,
+				k,
+				l,
+				m,
+				face = [];
 
-		var output = '';
+			var parseMesh = function ( mesh ) {
 
-		var indexVertex = 0;
-		var indexVertexUvs = 0;
-		var indexNormals = 0;
+				var nbVertex = 0;
+				var nbNormals = 0;
+				var nbVertexUvs = 0;
+				var geometry = mesh.geometry;
+				var normalMatrixWorld = new THREE.Matrix3();
 
-		var vertex = new THREE.Vector3();
-		var color = new THREE.Color();
-		var normal = new THREE.Vector3();
-		var uv = new THREE.Vector2();
+				if ( geometry.isBufferGeometry !== true ) {
 
-		var i, j, k, l, m, face = [];
+					throw new Error( 'THREE.OBJExporter: Geometry is not of type THREE.BufferGeometry.' );
 
-		var parseMesh = function ( mesh ) {
+				} // shortcuts
 
-			var nbVertex = 0;
-			var nbNormals = 0;
-			var nbVertexUvs = 0;
 
-			var geometry = mesh.geometry;
+				var vertices = geometry.getAttribute( 'position' );
+				var normals = geometry.getAttribute( 'normal' );
+				var uvs = geometry.getAttribute( 'uv' );
+				var indices = geometry.getIndex(); // name of the mesh object
 
-			var normalMatrixWorld = new THREE.Matrix3();
+				output += 'o ' + mesh.name + '\n'; // name of the mesh material
 
-			if ( geometry.isBufferGeometry !== true ) {
+				if ( mesh.material && mesh.material.name ) {
 
-				throw new Error( 'THREE.OBJExporter: Geometry is not of type THREE.BufferGeometry.' );
+					output += 'usemtl ' + mesh.material.name + '\n';
 
-			}
+				} // vertices
 
-			// shortcuts
-			var vertices = geometry.getAttribute( 'position' );
-			var normals = geometry.getAttribute( 'normal' );
-			var uvs = geometry.getAttribute( 'uv' );
-			var indices = geometry.getIndex();
 
-			// name of the mesh object
-			output += 'o ' + mesh.name + '\n';
+				if ( vertices !== undefined ) {
 
-			// name of the mesh material
-			if ( mesh.material && mesh.material.name ) {
+					for ( i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) {
 
-				output += 'usemtl ' + mesh.material.name + '\n';
+						vertex.x = vertices.getX( i );
+						vertex.y = vertices.getY( i );
+						vertex.z = vertices.getZ( i ); // transform the vertex to world space
 
-			}
+						vertex.applyMatrix4( mesh.matrixWorld ); // transform the vertex to export format
 
-			// vertices
+						output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n';
 
-			if ( vertices !== undefined ) {
-
-				for ( i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) {
-
-					vertex.x = vertices.getX( i );
-					vertex.y = vertices.getY( i );
-					vertex.z = vertices.getZ( i );
+					}
 
-					// transform the vertex to world space
-					vertex.applyMatrix4( mesh.matrixWorld );
+				} // uvs
 
-					// transform the vertex to export format
-					output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n';
 
-				}
+				if ( uvs !== undefined ) {
 
-			}
+					for ( i = 0, l = uvs.count; i < l; i ++, nbVertexUvs ++ ) {
 
-			// uvs
+						uv.x = uvs.getX( i );
+						uv.y = uvs.getY( i ); // transform the uv to export format
 
-			if ( uvs !== undefined ) {
+						output += 'vt ' + uv.x + ' ' + uv.y + '\n';
 
-				for ( i = 0, l = uvs.count; i < l; i ++, nbVertexUvs ++ ) {
+					}
 
-					uv.x = uvs.getX( i );
-					uv.y = uvs.getY( i );
+				} // normals
 
-					// transform the uv to export format
-					output += 'vt ' + uv.x + ' ' + uv.y + '\n';
 
-				}
+				if ( normals !== undefined ) {
 
-			}
+					normalMatrixWorld.getNormalMatrix( mesh.matrixWorld );
 
-			// normals
+					for ( i = 0, l = normals.count; i < l; i ++, nbNormals ++ ) {
 
-			if ( normals !== undefined ) {
+						normal.x = normals.getX( i );
+						normal.y = normals.getY( i );
+						normal.z = normals.getZ( i ); // transform the normal to world space
 
-				normalMatrixWorld.getNormalMatrix( mesh.matrixWorld );
+						normal.applyMatrix3( normalMatrixWorld ).normalize(); // transform the normal to export format
 
-				for ( i = 0, l = normals.count; i < l; i ++, nbNormals ++ ) {
+						output += 'vn ' + normal.x + ' ' + normal.y + ' ' + normal.z + '\n';
 
-					normal.x = normals.getX( i );
-					normal.y = normals.getY( i );
-					normal.z = normals.getZ( i );
+					}
 
-					// transform the normal to world space
-					normal.applyMatrix3( normalMatrixWorld ).normalize();
+				} // faces
 
-					// transform the normal to export format
-					output += 'vn ' + normal.x + ' ' + normal.y + ' ' + normal.z + '\n';
 
-				}
+				if ( indices !== null ) {
 
-			}
+					for ( i = 0, l = indices.count; i < l; i += 3 ) {
 
-			// faces
+						for ( m = 0; m < 3; m ++ ) {
 
-			if ( indices !== null ) {
+							j = indices.getX( i + m ) + 1;
+							face[ m ] = indexVertex + j + ( normals || uvs ? '/' + ( uvs ? indexVertexUvs + j : '' ) + ( normals ? '/' + ( indexNormals + j ) : '' ) : '' );
 
-				for ( i = 0, l = indices.count; i < l; i += 3 ) {
+						} // transform the face to export format
 
-					for ( m = 0; m < 3; m ++ ) {
 
-						j = indices.getX( i + m ) + 1;
-
-						face[ m ] = ( indexVertex + j ) + ( normals || uvs ? '/' + ( uvs ? ( indexVertexUvs + j ) : '' ) + ( normals ? '/' + ( indexNormals + j ) : '' ) : '' );
+						output += 'f ' + face.join( ' ' ) + '\n';
 
 					}
 
-					// transform the face to export format
-					output += 'f ' + face.join( ' ' ) + '\n';
+				} else {
 
-				}
+					for ( i = 0, l = vertices.count; i < l; i += 3 ) {
 
-			} else {
+						for ( m = 0; m < 3; m ++ ) {
 
-				for ( i = 0, l = vertices.count; i < l; i += 3 ) {
+							j = i + m + 1;
+							face[ m ] = indexVertex + j + ( normals || uvs ? '/' + ( uvs ? indexVertexUvs + j : '' ) + ( normals ? '/' + ( indexNormals + j ) : '' ) : '' );
 
-					for ( m = 0; m < 3; m ++ ) {
+						} // transform the face to export format
 
-						j = i + m + 1;
 
-						face[ m ] = ( indexVertex + j ) + ( normals || uvs ? '/' + ( uvs ? ( indexVertexUvs + j ) : '' ) + ( normals ? '/' + ( indexNormals + j ) : '' ) : '' );
+						output += 'f ' + face.join( ' ' ) + '\n';
 
 					}
 
-					// transform the face to export format
-					output += 'f ' + face.join( ' ' ) + '\n';
+				} // update index
 
-				}
 
-			}
+				indexVertex += nbVertex;
+				indexVertexUvs += nbVertexUvs;
+				indexNormals += nbNormals;
 
-			// update index
-			indexVertex += nbVertex;
-			indexVertexUvs += nbVertexUvs;
-			indexNormals += nbNormals;
+			};
 
-		};
+			var parseLine = function ( line ) {
 
-		var parseLine = function ( line ) {
+				var nbVertex = 0;
+				var geometry = line.geometry;
+				var type = line.type;
 
-			var nbVertex = 0;
+				if ( geometry.isBufferGeometry !== true ) {
 
-			var geometry = line.geometry;
-			var type = line.type;
+					throw new Error( 'THREE.OBJExporter: Geometry is not of type THREE.BufferGeometry.' );
 
-			if ( geometry.isBufferGeometry !== true ) {
+				} // shortcuts
 
-				throw new Error( 'THREE.OBJExporter: Geometry is not of type THREE.BufferGeometry.' );
 
-			}
+				var vertices = geometry.getAttribute( 'position' ); // name of the line object
 
-			// shortcuts
-			var vertices = geometry.getAttribute( 'position' );
+				output += 'o ' + line.name + '\n';
 
-			// name of the line object
-			output += 'o ' + line.name + '\n';
+				if ( vertices !== undefined ) {
 
-			if ( vertices !== undefined ) {
+					for ( i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) {
 
-				for ( i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) {
+						vertex.x = vertices.getX( i );
+						vertex.y = vertices.getY( i );
+						vertex.z = vertices.getZ( i ); // transform the vertex to world space
 
-					vertex.x = vertices.getX( i );
-					vertex.y = vertices.getY( i );
-					vertex.z = vertices.getZ( i );
+						vertex.applyMatrix4( line.matrixWorld ); // transform the vertex to export format
 
-					// transform the vertex to world space
-					vertex.applyMatrix4( line.matrixWorld );
+						output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n';
 
-					// transform the vertex to export format
-					output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n';
+					}
 
 				}
 
-			}
+				if ( type === 'Line' ) {
 
-			if ( type === 'Line' ) {
-
-				output += 'l ';
-
-				for ( j = 1, l = vertices.count; j <= l; j ++ ) {
+					output += 'l ';
 
-					output += ( indexVertex + j ) + ' ';
+					for ( j = 1, l = vertices.count; j <= l; j ++ ) {
 
-				}
+						output += indexVertex + j + ' ';
 
-				output += '\n';
+					}
 
-			}
+					output += '\n';
 
-			if ( type === 'LineSegments' ) {
+				}
 
-				for ( j = 1, k = j + 1, l = vertices.count; j < l; j += 2, k = j + 1 ) {
+				if ( type === 'LineSegments' ) {
 
-					output += 'l ' + ( indexVertex + j ) + ' ' + ( indexVertex + k ) + '\n';
+					for ( j = 1, k = j + 1, l = vertices.count; j < l; j += 2, k = j + 1 ) {
 
-				}
+						output += 'l ' + ( indexVertex + j ) + ' ' + ( indexVertex + k ) + '\n';
 
-			}
+					}
 
-			// update index
-			indexVertex += nbVertex;
+				} // update index
 
-		};
 
-		var parsePoints = function ( points ) {
+				indexVertex += nbVertex;
 
-			var nbVertex = 0;
+			};
 
-			var geometry = points.geometry;
+			var parsePoints = function ( points ) {
 
-			if ( geometry.isBufferGeometry !== true ) {
+				var nbVertex = 0;
+				var geometry = points.geometry;
 
-				throw new Error( 'THREE.OBJExporter: Geometry is not of type THREE.BufferGeometry.' );
+				if ( geometry.isBufferGeometry !== true ) {
 
-			}
+					throw new Error( 'THREE.OBJExporter: Geometry is not of type THREE.BufferGeometry.' );
 
-			var vertices = geometry.getAttribute( 'position' );
-			var colors = geometry.getAttribute( 'color' );
+				}
 
-			output += 'o ' + points.name + '\n';
+				var vertices = geometry.getAttribute( 'position' );
+				var colors = geometry.getAttribute( 'color' );
+				output += 'o ' + points.name + '\n';
 
-			if ( vertices !== undefined ) {
+				if ( vertices !== undefined ) {
 
-				for ( i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) {
+					for ( i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) {
 
-					vertex.fromBufferAttribute( vertices, i );
-					vertex.applyMatrix4( points.matrixWorld );
+						vertex.fromBufferAttribute( vertices, i );
+						vertex.applyMatrix4( points.matrixWorld );
+						output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z;
 
-					output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z;
+						if ( colors !== undefined ) {
 
-					if ( colors !== undefined ) {
+							color.fromBufferAttribute( colors, i );
+							output += ' ' + color.r + ' ' + color.g + ' ' + color.b;
 
-						color.fromBufferAttribute( colors, i );
+						}
 
-						output += ' ' + color.r + ' ' + color.g + ' ' + color.b;
+						output += '\n';
 
 					}
 
-					output += '\n';
-
 				}
 
-			}
-
-			output += 'p ';
+				output += 'p ';
 
-			for ( j = 1, l = vertices.count; j <= l; j ++ ) {
+				for ( j = 1, l = vertices.count; j <= l; j ++ ) {
 
-				output += ( indexVertex + j ) + ' ';
+					output += indexVertex + j + ' ';
 
-			}
+				}
 
-			output += '\n';
+				output += '\n'; // update index
 
-			// update index
-			indexVertex += nbVertex;
+				indexVertex += nbVertex;
 
-		};
+			};
 
-		object.traverse( function ( child ) {
+			object.traverse( function ( child ) {
 
-			if ( child.isMesh === true ) {
+				if ( child.isMesh === true ) {
 
-				parseMesh( child );
+					parseMesh( child );
 
-			}
+				}
 
-			if ( child.isLine === true ) {
+				if ( child.isLine === true ) {
 
-				parseLine( child );
+					parseLine( child );
 
-			}
+				}
 
-			if ( child.isPoints === true ) {
+				if ( child.isPoints === true ) {
 
-				parsePoints( child );
+					parsePoints( child );
 
-			}
+				}
 
-		} );
+			} );
+			return output;
 
-		return output;
+		}
+	};
 
-	}
+	THREE.OBJExporter = OBJExporter;
 
-};
+} )();

+ 271 - 354
examples/js/exporters/PLYExporter.js

@@ -1,523 +1,440 @@
-/**
+( function () {
+
+	/**
  * https://github.com/gkjohnson/ply-exporter-js
  *
  * Usage:
- *  var exporter = new THREE.PLYExporter();
+ *	var exporter = new PLYExporter();
  *
- *  // second argument is a list of options
- *  exporter.parse(mesh, data => console.log(data), { binary: true, excludeAttributes: [ 'color' ], littleEndian: true });
+ *	// second argument is a list of options
+ *	exporter.parse(mesh, data => console.log(data), { binary: true, excludeAttributes: [ 'color' ], littleEndian: true });
  *
  * Format Definition:
  * http://paulbourke.net/dataformats/ply/
  */
 
-THREE.PLYExporter = function () {};
+	var PLYExporter = function () {};
 
-THREE.PLYExporter.prototype = {
+	PLYExporter.prototype = {
+		constructor: PLYExporter,
+		parse: function ( object, onDone, options ) {
 
-	constructor: THREE.PLYExporter,
+			if ( onDone && typeof onDone === 'object' ) {
 
-	parse: function ( object, onDone, options ) {
+				console.warn( 'THREE.PLYExporter: The options parameter is now the third argument to the "parse" function. See the documentation for the new API.' );
+				options = onDone;
+				onDone = undefined;
 
-		if ( onDone && typeof onDone === 'object' ) {
+			} // Iterate over the valid meshes in the object
 
-			console.warn( 'THREE.PLYExporter: The options parameter is now the third argument to the "parse" function. See the documentation for the new API.' );
-			options = onDone;
-			onDone = undefined;
 
-		}
+			function traverseMeshes( cb ) {
 
-		// Iterate over the valid meshes in the object
-		function traverseMeshes( cb ) {
+				object.traverse( function ( child ) {
 
-			object.traverse( function ( child ) {
+					if ( child.isMesh === true ) {
 
-				if ( child.isMesh === true ) {
+						var mesh = child;
+						var geometry = mesh.geometry;
 
-					var mesh = child;
-					var geometry = mesh.geometry;
+						if ( geometry.isBufferGeometry !== true ) {
 
-					if ( geometry.isBufferGeometry !== true ) {
+							throw new Error( 'THREE.PLYExporter: Geometry is not of type THREE.BufferGeometry.' );
 
-						throw new Error( 'THREE.PLYExporter: Geometry is not of type THREE.BufferGeometry.' );
+						}
 
-					}
+						if ( geometry.hasAttribute( 'position' ) === true ) {
 
-					if ( geometry.hasAttribute( 'position' ) === true ) {
+							cb( mesh, geometry );
 
-						cb( mesh, geometry );
+						}
 
 					}
 
-				}
+				} );
 
-			} );
+			} // Default options
 
-		}
 
-		// Default options
-		var defaultOptions = {
-			binary: false,
-			excludeAttributes: [], // normal, uv, color, index
-			littleEndian: false
-		};
+			var defaultOptions = {
+				binary: false,
+				excludeAttributes: [],
+				// normal, uv, color, index
+				littleEndian: false
+			};
+			options = Object.assign( defaultOptions, options );
+			var excludeAttributes = options.excludeAttributes;
+			var includeNormals = false;
+			var includeColors = false;
+			var includeUVs = false; // count the vertices, check which properties are used,
+			// and cache the BufferGeometry
 
-		options = Object.assign( defaultOptions, options );
+			var vertexCount = 0;
+			var faceCount = 0;
+			object.traverse( function ( child ) {
 
-		var excludeAttributes = options.excludeAttributes;
-		var includeNormals = false;
-		var includeColors = false;
-		var includeUVs = false;
+				if ( child.isMesh === true ) {
 
-		// count the vertices, check which properties are used,
-		// and cache the BufferGeometry
-		var vertexCount = 0;
-		var faceCount = 0;
-		object.traverse( function ( child ) {
+					var mesh = child;
+					var geometry = mesh.geometry;
 
-			if ( child.isMesh === true ) {
+					if ( geometry.isBufferGeometry !== true ) {
 
-				var mesh = child;
-				var geometry = mesh.geometry;
+						throw new Error( 'THREE.PLYExporter: Geometry is not of type THREE.BufferGeometry.' );
 
-				if ( geometry.isBufferGeometry !== true ) {
+					}
 
-					throw new Error( 'THREE.PLYExporter: Geometry is not of type THREE.BufferGeometry.' );
+					var vertices = geometry.getAttribute( 'position' );
+					var normals = geometry.getAttribute( 'normal' );
+					var uvs = geometry.getAttribute( 'uv' );
+					var colors = geometry.getAttribute( 'color' );
+					var indices = geometry.getIndex();
 
-				}
+					if ( vertices === undefined ) {
 
-				var vertices = geometry.getAttribute( 'position' );
-				var normals = geometry.getAttribute( 'normal' );
-				var uvs = geometry.getAttribute( 'uv' );
-				var colors = geometry.getAttribute( 'color' );
-				var indices = geometry.getIndex();
+						return;
 
-				if ( vertices === undefined ) {
+					}
 
-					return;
+					vertexCount += vertices.count;
+					faceCount += indices ? indices.count / 3 : vertices.count / 3;
+					if ( normals !== undefined ) includeNormals = true;
+					if ( uvs !== undefined ) includeUVs = true;
+					if ( colors !== undefined ) includeColors = true;
 
 				}
 
-				vertexCount += vertices.count;
-				faceCount += indices ? indices.count / 3 : vertices.count / 3;
-
-				if ( normals !== undefined ) includeNormals = true;
+			} );
+			var includeIndices = excludeAttributes.indexOf( 'index' ) === - 1;
+			includeNormals = includeNormals && excludeAttributes.indexOf( 'normal' ) === - 1;
+			includeColors = includeColors && excludeAttributes.indexOf( 'color' ) === - 1;
+			includeUVs = includeUVs && excludeAttributes.indexOf( 'uv' ) === - 1;
 
-				if ( uvs !== undefined ) includeUVs = true;
+			if ( includeIndices && faceCount !== Math.floor( faceCount ) ) {
 
-				if ( colors !== undefined ) includeColors = true;
+				// point cloud meshes will not have an index array and may not have a
+				// number of vertices that is divisble by 3 (and therefore representable
+				// as triangles)
+				console.error( 'PLYExporter: Failed to generate a valid PLY file with triangle indices because the ' + 'number of indices is not divisible by 3.' );
+				return null;
 
 			}
 
-		} );
-
-		var includeIndices = excludeAttributes.indexOf( 'index' ) === - 1;
-		includeNormals = includeNormals && excludeAttributes.indexOf( 'normal' ) === - 1;
-		includeColors = includeColors && excludeAttributes.indexOf( 'color' ) === - 1;
-		includeUVs = includeUVs && excludeAttributes.indexOf( 'uv' ) === - 1;
-
-
-		if ( includeIndices && faceCount !== Math.floor( faceCount ) ) {
-
-			// point cloud meshes will not have an index array and may not have a
-			// number of vertices that is divisble by 3 (and therefore representable
-			// as triangles)
-			console.error(
-
-				'PLYExporter: Failed to generate a valid PLY file with triangle indices because the ' +
-				'number of indices is not divisible by 3.'
-
-			);
-
-			return null;
-
-		}
-
-		var indexByteCount = 4;
-
-		var header =
-			'ply\n' +
-			`format ${ options.binary ? ( options.littleEndian ? 'binary_little_endian' : 'binary_big_endian' ) : 'ascii' } 1.0\n` +
-			`element vertex ${vertexCount}\n` +
+			var indexByteCount = 4;
+			var header = 'ply\n' + `format ${options.binary ? options.littleEndian ? 'binary_little_endian' : 'binary_big_endian' : 'ascii'} 1.0\n` + `element vertex ${vertexCount}\n` + // position
+		'property float x\n' + 'property float y\n' + 'property float z\n';
 
-			// position
-			'property float x\n' +
-			'property float y\n' +
-			'property float z\n';
+			if ( includeNormals === true ) {
 
-		if ( includeNormals === true ) {
+				// normal
+				header += 'property float nx\n' + 'property float ny\n' + 'property float nz\n';
 
-			// normal
-			header +=
-				'property float nx\n' +
-				'property float ny\n' +
-				'property float nz\n';
-
-		}
-
-		if ( includeUVs === true ) {
-
-			// uvs
-			header +=
-				'property float s\n' +
-				'property float t\n';
-
-		}
-
-		if ( includeColors === true ) {
-
-			// colors
-			header +=
-				'property uchar red\n' +
-				'property uchar green\n' +
-				'property uchar blue\n';
-
-		}
+			}
 
-		if ( includeIndices === true ) {
+			if ( includeUVs === true ) {
 
-			// faces
-			header +=
-				`element face ${faceCount}\n` +
-				'property list uchar int vertex_index\n';
+				// uvs
+				header += 'property float s\n' + 'property float t\n';
 
-		}
+			}
 
-		header += 'end_header\n';
+			if ( includeColors === true ) {
 
+				// colors
+				header += 'property uchar red\n' + 'property uchar green\n' + 'property uchar blue\n';
 
-		// Generate attribute data
-		var vertex = new THREE.Vector3();
-		var normalMatrixWorld = new THREE.Matrix3();
-		var result = null;
+			}
 
-		if ( options.binary === true ) {
+			if ( includeIndices === true ) {
 
-			// Binary File Generation
-			var headerBin = new TextEncoder().encode( header );
+				// faces
+				header += `element face ${faceCount}\n` + 'property list uchar int vertex_index\n';
 
-			// 3 position values at 4 bytes
-			// 3 normal values at 4 bytes
-			// 3 color channels with 1 byte
-			// 2 uv values at 4 bytes
-			var vertexListLength = vertexCount * ( 4 * 3 + ( includeNormals ? 4 * 3 : 0 ) + ( includeColors ? 3 : 0 ) + ( includeUVs ? 4 * 2 : 0 ) );
+			}
 
-			// 1 byte shape desciptor
-			// 3 vertex indices at ${indexByteCount} bytes
-			var faceListLength = includeIndices ? faceCount * ( indexByteCount * 3 + 1 ) : 0;
-			var output = new DataView( new ArrayBuffer( headerBin.length + vertexListLength + faceListLength ) );
-			new Uint8Array( output.buffer ).set( headerBin, 0 );
+			header += 'end_header\n'; // Generate attribute data
 
+			var vertex = new THREE.Vector3();
+			var normalMatrixWorld = new THREE.Matrix3();
+			var result = null;
 
-			var vOffset = headerBin.length;
-			var fOffset = headerBin.length + vertexListLength;
-			var writtenVertices = 0;
-			traverseMeshes( function ( mesh, geometry ) {
+			if ( options.binary === true ) {
 
-				var vertices = geometry.getAttribute( 'position' );
-				var normals = geometry.getAttribute( 'normal' );
-				var uvs = geometry.getAttribute( 'uv' );
-				var colors = geometry.getAttribute( 'color' );
-				var indices = geometry.getIndex();
+				// Binary File Generation
+				var headerBin = new TextEncoder().encode( header ); // 3 position values at 4 bytes
+				// 3 normal values at 4 bytes
+				// 3 color channels with 1 byte
+				// 2 uv values at 4 bytes
 
-				normalMatrixWorld.getNormalMatrix( mesh.matrixWorld );
+				var vertexListLength = vertexCount * ( 4 * 3 + ( includeNormals ? 4 * 3 : 0 ) + ( includeColors ? 3 : 0 ) + ( includeUVs ? 4 * 2 : 0 ) ); // 1 byte shape desciptor
+				// 3 vertex indices at ${indexByteCount} bytes
 
-				for ( var i = 0, l = vertices.count; i < l; i ++ ) {
+				var faceListLength = includeIndices ? faceCount * ( indexByteCount * 3 + 1 ) : 0;
+				var output = new DataView( new ArrayBuffer( headerBin.length + vertexListLength + faceListLength ) );
+				new Uint8Array( output.buffer ).set( headerBin, 0 );
+				var vOffset = headerBin.length;
+				var fOffset = headerBin.length + vertexListLength;
+				var writtenVertices = 0;
+				traverseMeshes( function ( mesh, geometry ) {
 
-					vertex.x = vertices.getX( i );
-					vertex.y = vertices.getY( i );
-					vertex.z = vertices.getZ( i );
+					var vertices = geometry.getAttribute( 'position' );
+					var normals = geometry.getAttribute( 'normal' );
+					var uvs = geometry.getAttribute( 'uv' );
+					var colors = geometry.getAttribute( 'color' );
+					var indices = geometry.getIndex();
+					normalMatrixWorld.getNormalMatrix( mesh.matrixWorld );
 
-					vertex.applyMatrix4( mesh.matrixWorld );
+					for ( var i = 0, l = vertices.count; i < l; i ++ ) {
 
+						vertex.x = vertices.getX( i );
+						vertex.y = vertices.getY( i );
+						vertex.z = vertices.getZ( i );
+						vertex.applyMatrix4( mesh.matrixWorld ); // Position information
 
-					// Position information
-					output.setFloat32( vOffset, vertex.x, options.littleEndian );
-					vOffset += 4;
+						output.setFloat32( vOffset, vertex.x, options.littleEndian );
+						vOffset += 4;
+						output.setFloat32( vOffset, vertex.y, options.littleEndian );
+						vOffset += 4;
+						output.setFloat32( vOffset, vertex.z, options.littleEndian );
+						vOffset += 4; // Normal information
 
-					output.setFloat32( vOffset, vertex.y, options.littleEndian );
-					vOffset += 4;
+						if ( includeNormals === true ) {
 
-					output.setFloat32( vOffset, vertex.z, options.littleEndian );
-					vOffset += 4;
+							if ( normals != null ) {
 
-					// Normal information
-					if ( includeNormals === true ) {
+								vertex.x = normals.getX( i );
+								vertex.y = normals.getY( i );
+								vertex.z = normals.getZ( i );
+								vertex.applyMatrix3( normalMatrixWorld ).normalize();
+								output.setFloat32( vOffset, vertex.x, options.littleEndian );
+								vOffset += 4;
+								output.setFloat32( vOffset, vertex.y, options.littleEndian );
+								vOffset += 4;
+								output.setFloat32( vOffset, vertex.z, options.littleEndian );
+								vOffset += 4;
 
-						if ( normals != null ) {
+							} else {
 
-							vertex.x = normals.getX( i );
-							vertex.y = normals.getY( i );
-							vertex.z = normals.getZ( i );
+								output.setFloat32( vOffset, 0, options.littleEndian );
+								vOffset += 4;
+								output.setFloat32( vOffset, 0, options.littleEndian );
+								vOffset += 4;
+								output.setFloat32( vOffset, 0, options.littleEndian );
+								vOffset += 4;
 
-							vertex.applyMatrix3( normalMatrixWorld ).normalize();
+							}
 
-							output.setFloat32( vOffset, vertex.x, options.littleEndian );
-							vOffset += 4;
+						} // UV information
 
-							output.setFloat32( vOffset, vertex.y, options.littleEndian );
-							vOffset += 4;
 
-							output.setFloat32( vOffset, vertex.z, options.littleEndian );
-							vOffset += 4;
+						if ( includeUVs === true ) {
 
-						} else {
+							if ( uvs != null ) {
 
-							output.setFloat32( vOffset, 0, options.littleEndian );
-							vOffset += 4;
+								output.setFloat32( vOffset, uvs.getX( i ), options.littleEndian );
+								vOffset += 4;
+								output.setFloat32( vOffset, uvs.getY( i ), options.littleEndian );
+								vOffset += 4;
 
-							output.setFloat32( vOffset, 0, options.littleEndian );
-							vOffset += 4;
+							} else if ( includeUVs !== false ) {
 
-							output.setFloat32( vOffset, 0, options.littleEndian );
-							vOffset += 4;
+								output.setFloat32( vOffset, 0, options.littleEndian );
+								vOffset += 4;
+								output.setFloat32( vOffset, 0, options.littleEndian );
+								vOffset += 4;
 
-						}
+							}
 
-					}
+						} // Color information
 
-					// UV information
-					if ( includeUVs === true ) {
 
-						if ( uvs != null ) {
+						if ( includeColors === true ) {
 
-							output.setFloat32( vOffset, uvs.getX( i ), options.littleEndian );
-							vOffset += 4;
+							if ( colors != null ) {
 
-							output.setFloat32( vOffset, uvs.getY( i ), options.littleEndian );
-							vOffset += 4;
+								output.setUint8( vOffset, Math.floor( colors.getX( i ) * 255 ) );
+								vOffset += 1;
+								output.setUint8( vOffset, Math.floor( colors.getY( i ) * 255 ) );
+								vOffset += 1;
+								output.setUint8( vOffset, Math.floor( colors.getZ( i ) * 255 ) );
+								vOffset += 1;
 
-						} else if ( includeUVs !== false ) {
+							} else {
 
-							output.setFloat32( vOffset, 0, options.littleEndian );
-							vOffset += 4;
+								output.setUint8( vOffset, 255 );
+								vOffset += 1;
+								output.setUint8( vOffset, 255 );
+								vOffset += 1;
+								output.setUint8( vOffset, 255 );
+								vOffset += 1;
 
-							output.setFloat32( vOffset, 0, options.littleEndian );
-							vOffset += 4;
+							}
 
 						}
 
 					}
 
-					// Color information
-					if ( includeColors === true ) {
+					if ( includeIndices === true ) {
 
-						if ( colors != null ) {
+						// Create the face list
+						if ( indices !== null ) {
 
-							output.setUint8( vOffset, Math.floor( colors.getX( i ) * 255 ) );
-							vOffset += 1;
+							for ( var i = 0, l = indices.count; i < l; i += 3 ) {
 
-							output.setUint8( vOffset, Math.floor( colors.getY( i ) * 255 ) );
-							vOffset += 1;
+								output.setUint8( fOffset, 3 );
+								fOffset += 1;
+								output.setUint32( fOffset, indices.getX( i + 0 ) + writtenVertices, options.littleEndian );
+								fOffset += indexByteCount;
+								output.setUint32( fOffset, indices.getX( i + 1 ) + writtenVertices, options.littleEndian );
+								fOffset += indexByteCount;
+								output.setUint32( fOffset, indices.getX( i + 2 ) + writtenVertices, options.littleEndian );
+								fOffset += indexByteCount;
 
-							output.setUint8( vOffset, Math.floor( colors.getZ( i ) * 255 ) );
-							vOffset += 1;
+							}
 
 						} else {
 
-							output.setUint8( vOffset, 255 );
-							vOffset += 1;
+							for ( var i = 0, l = vertices.count; i < l; i += 3 ) {
 
-							output.setUint8( vOffset, 255 );
-							vOffset += 1;
+								output.setUint8( fOffset, 3 );
+								fOffset += 1;
+								output.setUint32( fOffset, writtenVertices + i, options.littleEndian );
+								fOffset += indexByteCount;
+								output.setUint32( fOffset, writtenVertices + i + 1, options.littleEndian );
+								fOffset += indexByteCount;
+								output.setUint32( fOffset, writtenVertices + i + 2, options.littleEndian );
+								fOffset += indexByteCount;
 
-							output.setUint8( vOffset, 255 );
-							vOffset += 1;
+							}
 
 						}
 
-					}
-
-				}
-
-				if ( includeIndices === true ) {
-
-					// Create the face list
-
-					if ( indices !== null ) {
+					} // Save the amount of verts we've already written so we can offset
+					// the face index on the next mesh
 
-						for ( var i = 0, l = indices.count; i < l; i += 3 ) {
 
-							output.setUint8( fOffset, 3 );
-							fOffset += 1;
+					writtenVertices += vertices.count;
 
-							output.setUint32( fOffset, indices.getX( i + 0 ) + writtenVertices, options.littleEndian );
-							fOffset += indexByteCount;
+				} );
+				result = output.buffer;
 
-							output.setUint32( fOffset, indices.getX( i + 1 ) + writtenVertices, options.littleEndian );
-							fOffset += indexByteCount;
+			} else {
 
-							output.setUint32( fOffset, indices.getX( i + 2 ) + writtenVertices, options.littleEndian );
-							fOffset += indexByteCount;
+				// Ascii File Generation
+				// count the number of vertices
+				var writtenVertices = 0;
+				var vertexList = '';
+				var faceList = '';
+				traverseMeshes( function ( mesh, geometry ) {
 
-						}
-
-					} else {
-
-						for ( var i = 0, l = vertices.count; i < l; i += 3 ) {
-
-							output.setUint8( fOffset, 3 );
-							fOffset += 1;
-
-							output.setUint32( fOffset, writtenVertices + i, options.littleEndian );
-							fOffset += indexByteCount;
-
-							output.setUint32( fOffset, writtenVertices + i + 1, options.littleEndian );
-							fOffset += indexByteCount;
+					var vertices = geometry.getAttribute( 'position' );
+					var normals = geometry.getAttribute( 'normal' );
+					var uvs = geometry.getAttribute( 'uv' );
+					var colors = geometry.getAttribute( 'color' );
+					var indices = geometry.getIndex();
+					normalMatrixWorld.getNormalMatrix( mesh.matrixWorld ); // form each line
 
-							output.setUint32( fOffset, writtenVertices + i + 2, options.littleEndian );
-							fOffset += indexByteCount;
+					for ( var i = 0, l = vertices.count; i < l; i ++ ) {
 
-						}
+						vertex.x = vertices.getX( i );
+						vertex.y = vertices.getY( i );
+						vertex.z = vertices.getZ( i );
+						vertex.applyMatrix4( mesh.matrixWorld ); // Position information
 
-					}
+						var line = vertex.x + ' ' + vertex.y + ' ' + vertex.z; // Normal information
 
-				}
+						if ( includeNormals === true ) {
 
+							if ( normals != null ) {
 
-				// Save the amount of verts we've already written so we can offset
-				// the face index on the next mesh
-				writtenVertices += vertices.count;
+								vertex.x = normals.getX( i );
+								vertex.y = normals.getY( i );
+								vertex.z = normals.getZ( i );
+								vertex.applyMatrix3( normalMatrixWorld ).normalize();
+								line += ' ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z;
 
-			} );
+							} else {
 
-			result = output.buffer;
+								line += ' 0 0 0';
 
-		} else {
+							}
 
-			// Ascii File Generation
-			// count the number of vertices
-			var writtenVertices = 0;
-			var vertexList = '';
-			var faceList = '';
+						} // UV information
 
-			traverseMeshes( function ( mesh, geometry ) {
 
-				var vertices = geometry.getAttribute( 'position' );
-				var normals = geometry.getAttribute( 'normal' );
-				var uvs = geometry.getAttribute( 'uv' );
-				var colors = geometry.getAttribute( 'color' );
-				var indices = geometry.getIndex();
+						if ( includeUVs === true ) {
 
-				normalMatrixWorld.getNormalMatrix( mesh.matrixWorld );
+							if ( uvs != null ) {
 
-				// form each line
-				for ( var i = 0, l = vertices.count; i < l; i ++ ) {
+								line += ' ' + uvs.getX( i ) + ' ' + uvs.getY( i );
 
-					vertex.x = vertices.getX( i );
-					vertex.y = vertices.getY( i );
-					vertex.z = vertices.getZ( i );
+							} else if ( includeUVs !== false ) {
 
-					vertex.applyMatrix4( mesh.matrixWorld );
+								line += ' 0 0';
 
+							}
 
-					// Position information
-					var line =
-						vertex.x + ' ' +
-						vertex.y + ' ' +
-						vertex.z;
+						} // Color information
 
-					// Normal information
-					if ( includeNormals === true ) {
 
-						if ( normals != null ) {
+						if ( includeColors === true ) {
 
-							vertex.x = normals.getX( i );
-							vertex.y = normals.getY( i );
-							vertex.z = normals.getZ( i );
+							if ( colors != null ) {
 
-							vertex.applyMatrix3( normalMatrixWorld ).normalize();
+								line += ' ' + Math.floor( colors.getX( i ) * 255 ) + ' ' + Math.floor( colors.getY( i ) * 255 ) + ' ' + Math.floor( colors.getZ( i ) * 255 );
 
-							line += ' ' +
-								vertex.x + ' ' +
-								vertex.y + ' ' +
-								vertex.z;
+							} else {
 
-						} else {
+								line += ' 255 255 255';
 
-							line += ' 0 0 0';
+							}
 
 						}
 
-					}
-
-					// UV information
-					if ( includeUVs === true ) {
-
-						if ( uvs != null ) {
-
-							line += ' ' +
-								uvs.getX( i ) + ' ' +
-								uvs.getY( i );
+						vertexList += line + '\n';
 
-						} else if ( includeUVs !== false ) {
+					} // Create the face list
 
-							line += ' 0 0';
 
-						}
+					if ( includeIndices === true ) {
 
-					}
+						if ( indices !== null ) {
 
-					// Color information
-					if ( includeColors === true ) {
+							for ( var i = 0, l = indices.count; i < l; i += 3 ) {
 
-						if ( colors != null ) {
+								faceList += `3 ${indices.getX( i + 0 ) + writtenVertices}`;
+								faceList += ` ${indices.getX( i + 1 ) + writtenVertices}`;
+								faceList += ` ${indices.getX( i + 2 ) + writtenVertices}\n`;
 
-							line += ' ' +
-								Math.floor( colors.getX( i ) * 255 ) + ' ' +
-								Math.floor( colors.getY( i ) * 255 ) + ' ' +
-								Math.floor( colors.getZ( i ) * 255 );
+							}
 
 						} else {
 
-							line += ' 255 255 255';
-
-						}
-
-					}
-
-					vertexList += line + '\n';
-
-				}
-
-				// Create the face list
-				if ( includeIndices === true ) {
-
-					if ( indices !== null ) {
+							for ( var i = 0, l = vertices.count; i < l; i += 3 ) {
 
-						for ( var i = 0, l = indices.count; i < l; i += 3 ) {
+								faceList += `3 ${writtenVertices + i} ${writtenVertices + i + 1} ${writtenVertices + i + 2}\n`;
 
-							faceList += `3 ${ indices.getX( i + 0 ) + writtenVertices }`;
-							faceList += ` ${ indices.getX( i + 1 ) + writtenVertices }`;
-							faceList += ` ${ indices.getX( i + 2 ) + writtenVertices }\n`;
+							}
 
 						}
 
-					} else {
-
-						for ( var i = 0, l = vertices.count; i < l; i += 3 ) {
-
-							faceList += `3 ${ writtenVertices + i } ${ writtenVertices + i + 1 } ${ writtenVertices + i + 2 }\n`;
-
-						}
+						faceCount += indices ? indices.count / 3 : vertices.count / 3;
 
 					}
 
-					faceCount += indices ? indices.count / 3 : vertices.count / 3;
-
-				}
+					writtenVertices += vertices.count;
 
-				writtenVertices += vertices.count;
+				} );
+				result = `${header}${vertexList}${includeIndices ? `${faceList}\n` : '\n'}`;
 
-			} );
+			}
 
-			result = `${ header }${vertexList}${ includeIndices ? `${faceList}\n` : '\n' }`;
+			if ( typeof onDone === 'function' ) requestAnimationFrame( () => onDone( result ) );
+			return result;
 
 		}
+	};
 
-		if ( typeof onDone === 'function' ) requestAnimationFrame( () => onDone( result ) );
-		return result;
-
-	}
+	THREE.PLYExporter = PLYExporter;
 
-};
+} )();

+ 124 - 128
examples/js/exporters/STLExporter.js

@@ -1,203 +1,199 @@
-/**
+( function () {
+
+	/**
  * Usage:
- *  var exporter = new THREE.STLExporter();
+ *	var exporter = new STLExporter();
  *
- *  // second argument is a list of options
- *  var data = exporter.parse( mesh, { binary: true } );
+ *	// second argument is a list of options
+ *	var data = exporter.parse( mesh, { binary: true } );
  *
  */
 
-THREE.STLExporter = function () {};
-
-THREE.STLExporter.prototype = {
-
-	constructor: THREE.STLExporter,
+	var STLExporter = function () {};
 
-	parse: function ( scene, options ) {
+	STLExporter.prototype = {
+		constructor: STLExporter,
+		parse: function ( scene, options ) {
 
-		if ( options === undefined ) options = {};
+			if ( options === undefined ) options = {};
+			var binary = options.binary !== undefined ? options.binary : false; //
 
-		var binary = options.binary !== undefined ? options.binary : false;
+			var objects = [];
+			var triangles = 0;
+			scene.traverse( function ( object ) {
 
-		//
+				if ( object.isMesh ) {
 
-		var objects = [];
-		var triangles = 0;
+					var geometry = object.geometry;
 
-		scene.traverse( function ( object ) {
+					if ( geometry.isBufferGeometry !== true ) {
 
-			if ( object.isMesh ) {
+						throw new Error( 'THREE.STLExporter: Geometry is not of type THREE.BufferGeometry.' );
 
-				var geometry = object.geometry;
+					}
 
-				if ( geometry.isBufferGeometry !== true ) {
-
-					throw new Error( 'THREE.STLExporter: Geometry is not of type THREE.BufferGeometry.' );
+					var index = geometry.index;
+					var positionAttribute = geometry.getAttribute( 'position' );
+					triangles += index !== null ? index.count / 3 : positionAttribute.count / 3;
+					objects.push( {
+						object3d: object,
+						geometry: geometry
+					} );
 
 				}
 
-				var index = geometry.index;
-				var positionAttribute = geometry.getAttribute( 'position' );
+			} );
+			var output;
+			var offset = 80; // skip header
 
-				triangles += ( index !== null ) ? ( index.count / 3 ) : ( positionAttribute.count / 3 );
-
-				objects.push( {
-					object3d: object,
-					geometry: geometry
-				} );
-
-			}
-
-		} );
+			if ( binary === true ) {
 
-		var output;
-		var offset = 80; // skip header
+				var bufferLength = triangles * 2 + triangles * 3 * 4 * 4 + 80 + 4;
+				var arrayBuffer = new ArrayBuffer( bufferLength );
+				output = new DataView( arrayBuffer );
+				output.setUint32( offset, triangles, true );
+				offset += 4;
 
-		if ( binary === true ) {
+			} else {
 
-			var bufferLength = triangles * 2 + triangles * 3 * 4 * 4 + 80 + 4;
-			var arrayBuffer = new ArrayBuffer( bufferLength );
-			output = new DataView( arrayBuffer );
-			output.setUint32( offset, triangles, true ); offset += 4;
+				output = '';
+				output += 'solid exported\n';
 
-		} else {
+			}
 
-			output = '';
-			output += 'solid exported\n';
+			var vA = new THREE.Vector3();
+			var vB = new THREE.Vector3();
+			var vC = new THREE.Vector3();
+			var cb = new THREE.Vector3();
+			var ab = new THREE.Vector3();
+			var normal = new THREE.Vector3();
 
-		}
+			for ( var i = 0, il = objects.length; i < il; i ++ ) {
 
-		var vA = new THREE.Vector3();
-		var vB = new THREE.Vector3();
-		var vC = new THREE.Vector3();
-		var cb = new THREE.Vector3();
-		var ab = new THREE.Vector3();
-		var normal = new THREE.Vector3();
+				var object = objects[ i ].object3d;
+				var geometry = objects[ i ].geometry;
+				var index = geometry.index;
+				var positionAttribute = geometry.getAttribute( 'position' );
 
-		for ( var i = 0, il = objects.length; i < il; i ++ ) {
+				if ( index !== null ) {
 
-			var object = objects[ i ].object3d;
-			var geometry = objects[ i ].geometry;
+					// indexed geometry
+					for ( var j = 0; j < index.count; j += 3 ) {
 
-			var index = geometry.index;
-			var positionAttribute = geometry.getAttribute( 'position' );
+						var a = index.getX( j + 0 );
+						var b = index.getX( j + 1 );
+						var c = index.getX( j + 2 );
+						writeFace( a, b, c, positionAttribute, object );
 
-			if ( index !== null ) {
+					}
 
-				// indexed geometry
+				} else {
 
-				for ( var j = 0; j < index.count; j += 3 ) {
+					// non-indexed geometry
+					for ( var j = 0; j < positionAttribute.count; j += 3 ) {
 
-					var a = index.getX( j + 0 );
-					var b = index.getX( j + 1 );
-					var c = index.getX( j + 2 );
+						var a = j + 0;
+						var b = j + 1;
+						var c = j + 2;
+						writeFace( a, b, c, positionAttribute, object );
 
-					writeFace( a, b, c, positionAttribute, object );
-
-				}
-
-			} else {
-
-				// non-indexed geometry
-
-				for ( var j = 0; j < positionAttribute.count; j += 3 ) {
-
-					var a = j + 0;
-					var b = j + 1;
-					var c = j + 2;
-
-					writeFace( a, b, c, positionAttribute, object );
+					}
 
 				}
 
 			}
 
-		}
-
-		if ( binary === false ) {
-
-			output += 'endsolid exported\n';
+			if ( binary === false ) {
 
-		}
+				output += 'endsolid exported\n';
 
-		return output;
+			}
 
-		function writeFace( a, b, c, positionAttribute, object ) {
+			return output;
 
-			vA.fromBufferAttribute( positionAttribute, a );
-			vB.fromBufferAttribute( positionAttribute, b );
-			vC.fromBufferAttribute( positionAttribute, c );
+			function writeFace( a, b, c, positionAttribute, object ) {
 
-			if ( object.isSkinnedMesh === true ) {
+				vA.fromBufferAttribute( positionAttribute, a );
+				vB.fromBufferAttribute( positionAttribute, b );
+				vC.fromBufferAttribute( positionAttribute, c );
 
-				object.boneTransform( a, vA );
-				object.boneTransform( b, vB );
-				object.boneTransform( c, vC );
+				if ( object.isSkinnedMesh === true ) {
 
-			}
+					object.boneTransform( a, vA );
+					object.boneTransform( b, vB );
+					object.boneTransform( c, vC );
 
-			vA.applyMatrix4( object.matrixWorld );
-			vB.applyMatrix4( object.matrixWorld );
-			vC.applyMatrix4( object.matrixWorld );
+				}
 
-			writeNormal( vA, vB, vC );
+				vA.applyMatrix4( object.matrixWorld );
+				vB.applyMatrix4( object.matrixWorld );
+				vC.applyMatrix4( object.matrixWorld );
+				writeNormal( vA, vB, vC );
+				writeVertex( vA );
+				writeVertex( vB );
+				writeVertex( vC );
 
-			writeVertex( vA );
-			writeVertex( vB );
-			writeVertex( vC );
+				if ( binary === true ) {
 
-			if ( binary === true ) {
+					output.setUint16( offset, 0, true );
+					offset += 2;
 
-				output.setUint16( offset, 0, true ); offset += 2;
+				} else {
 
-			} else {
+					output += '\t\tendloop\n';
+					output += '\tendfacet\n';
 
-				output += '\t\tendloop\n';
-				output += '\tendfacet\n';
+				}
 
 			}
 
-		}
+			function writeNormal( vA, vB, vC ) {
 
-		function writeNormal( vA, vB, vC ) {
+				cb.subVectors( vC, vB );
+				ab.subVectors( vA, vB );
+				cb.cross( ab ).normalize();
+				normal.copy( cb ).normalize();
 
-			cb.subVectors( vC, vB );
-			ab.subVectors( vA, vB );
-			cb.cross( ab ).normalize();
+				if ( binary === true ) {
 
-			normal.copy( cb ).normalize();
+					output.setFloat32( offset, normal.x, true );
+					offset += 4;
+					output.setFloat32( offset, normal.y, true );
+					offset += 4;
+					output.setFloat32( offset, normal.z, true );
+					offset += 4;
 
-			if ( binary === true ) {
+				} else {
 
-				output.setFloat32( offset, normal.x, true ); offset += 4;
-				output.setFloat32( offset, normal.y, true ); offset += 4;
-				output.setFloat32( offset, normal.z, true ); offset += 4;
-
-			} else {
+					output += '\tfacet normal ' + normal.x + ' ' + normal.y + ' ' + normal.z + '\n';
+					output += '\t\touter loop\n';
 
-				output += '\tfacet normal ' + normal.x + ' ' + normal.y + ' ' + normal.z + '\n';
-				output += '\t\touter loop\n';
+				}
 
 			}
 
-		}
+			function writeVertex( vertex ) {
 
-		function writeVertex( vertex ) {
+				if ( binary === true ) {
 
-			if ( binary === true ) {
+					output.setFloat32( offset, vertex.x, true );
+					offset += 4;
+					output.setFloat32( offset, vertex.y, true );
+					offset += 4;
+					output.setFloat32( offset, vertex.z, true );
+					offset += 4;
 
-				output.setFloat32( offset, vertex.x, true ); offset += 4;
-				output.setFloat32( offset, vertex.y, true ); offset += 4;
-				output.setFloat32( offset, vertex.z, true ); offset += 4;
+				} else {
 
-			} else {
+					output += '\t\t\tvertex ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n';
 
-				output += '\t\t\tvertex ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n';
+				}
 
 			}
 
 		}
+	};
 
-	}
+	THREE.STLExporter = STLExporter;
 
-};
+} )();

+ 386 - 0
examples/js/exporters/USDZExporter.js

@@ -0,0 +1,386 @@
+( function () {
+
+	class USDZExporter {
+
+		async parse( scene ) {
+
+			let output = buildHeader();
+			const materials = {};
+			const textures = {};
+			scene.traverse( object => {
+
+				if ( object.isMesh ) {
+
+					const geometry = object.geometry;
+					const material = object.material;
+					materials[ material.uuid ] = material;
+					if ( material.map !== null ) textures[ material.map.uuid ] = material.map;
+					if ( material.normalMap !== null ) textures[ material.normalMap.uuid ] = material.normalMap;
+					if ( material.aoMap !== null ) textures[ material.aoMap.uuid ] = material.aoMap;
+					if ( material.roughnessMap !== null ) textures[ material.roughnessMap.uuid ] = material.roughnessMap;
+					if ( material.metalnessMap !== null ) textures[ material.metalnessMap.uuid ] = material.metalnessMap;
+					if ( material.emissiveMap !== null ) textures[ material.emissiveMap.uuid ] = material.emissiveMap;
+					output += buildXform( object, buildMesh( geometry, material ) );
+
+				}
+
+			} );
+			output += buildMaterials( materials );
+			output += buildTextures( textures );
+			const files = {
+				'model.usda': fflate.strToU8( output )
+			};
+
+			for ( const uuid in textures ) {
+
+				const texture = textures[ uuid ];
+				files[ 'textures/Texture_' + texture.id + '.jpg' ] = await imgToU8( texture.image );
+
+			} // 64 byte alignment
+			// https://github.com/101arrowz/fflate/issues/39#issuecomment-777263109
+
+
+			let offset = 0;
+
+			for ( const filename in files ) {
+
+				const file = files[ filename ];
+				const headerSize = 34 + filename.length;
+				offset += headerSize;
+				const offsetMod64 = offset & 63;
+
+				if ( offsetMod64 !== 4 ) {
+
+					const padLength = 64 - offsetMod64;
+					const padding = new Uint8Array( padLength );
+					files[ filename ] = [ file, {
+						extra: {
+							12345: padding
+						}
+					} ];
+
+				}
+
+				offset = file.length;
+
+			}
+
+			return fflate.zipSync( files, {
+				level: 0
+			} );
+
+		}
+
+	}
+
+	async function imgToU8( image ) {
+
+		if ( typeof HTMLImageElement !== 'undefined' && image instanceof HTMLImageElement || typeof HTMLCanvasElement !== 'undefined' && image instanceof HTMLCanvasElement || typeof OffscreenCanvas !== 'undefined' && image instanceof OffscreenCanvas || typeof ImageBitmap !== 'undefined' && image instanceof ImageBitmap ) {
+
+			const scale = 1024 / Math.max( image.width, image.height );
+			const canvas = document.createElement( 'canvas' );
+			canvas.width = image.width * Math.min( 1, scale );
+			canvas.height = image.height * Math.min( 1, scale );
+			const context = canvas.getContext( '2d' );
+			context.drawImage( image, 0, 0, canvas.width, canvas.height );
+			const blob = await new Promise( resolve => canvas.toBlob( resolve, 'image/jpeg', 1 ) );
+			return new Uint8Array( await blob.arrayBuffer() );
+
+		}
+
+	} //
+
+
+	const PRECISION = 7;
+
+	function buildHeader() {
+
+		return `#usda 1.0
+(
+		customLayerData = {
+				string creator = "Three.js USDZExporter"
+		}
+		metersPerUnit = 1
+		upAxis = "Y"
+)
+
+`;
+
+	} // Xform
+
+
+	function buildXform( object, define ) {
+
+		const name = 'Object_' + object.id;
+		const transform = buildMatrix( object.matrixWorld );
+		return `def Xform "${name}"
+{
+		matrix4d xformOp:transform = ${transform}
+		uniform token[] xformOpOrder = ["xformOp:transform"]
+
+		${define}
+}
+
+`;
+
+	}
+
+	function buildMatrix( matrix ) {
+
+		const array = matrix.elements;
+		return `( ${buildMatrixRow( array, 0 )}, ${buildMatrixRow( array, 4 )}, ${buildMatrixRow( array, 8 )}, ${buildMatrixRow( array, 12 )} )`;
+
+	}
+
+	function buildMatrixRow( array, offset ) {
+
+		return `(${array[ offset + 0 ]}, ${array[ offset + 1 ]}, ${array[ offset + 2 ]}, ${array[ offset + 3 ]})`;
+
+	} // Mesh
+
+
+	function buildMesh( geometry, material ) {
+
+		const name = 'Geometry_' + geometry.id;
+		const attributes = geometry.attributes;
+		const count = attributes.position.count;
+
+		if ( 'uv2' in attributes ) {
+
+			console.warn( 'THREE.USDZExporter: uv2 not supported yet.' );
+
+		}
+
+		return `def Mesh "${name}"
+		{
+				int[] faceVertexCounts = [${buildMeshVertexCount( geometry )}]
+				int[] faceVertexIndices = [${buildMeshVertexIndices( geometry )}]
+				rel material:binding = </Materials/Material_${material.id}>
+				normal3f[] normals = [${buildVector3Array( attributes.normal, count )}] (
+						interpolation = "vertex"
+				)
+				point3f[] points = [${buildVector3Array( attributes.position, count )}]
+				float2[] primvars:st = [${buildVector2Array( attributes.uv, count )}] (
+						interpolation = "vertex"
+				)
+				uniform token subdivisionScheme = "none"
+		}
+`;
+
+	}
+
+	function buildMeshVertexCount( geometry ) {
+
+		const count = geometry.index !== null ? geometry.index.array.length : geometry.attributes.position.count;
+		return Array( count / 3 ).fill( 3 ).join( ', ' );
+
+	}
+
+	function buildMeshVertexIndices( geometry ) {
+
+		if ( geometry.index !== null ) {
+
+			return geometry.index.array.join( ', ' );
+
+		}
+
+		const array = [];
+		const length = geometry.attributes.position.count;
+
+		for ( let i = 0; i < length; i ++ ) {
+
+			array.push( i );
+
+		}
+
+		return array.join( ', ' );
+
+	}
+
+	function buildVector3Array( attribute, count ) {
+
+		if ( attribute === undefined ) {
+
+			console.warn( 'USDZExporter: Normals missing.' );
+			return Array( count ).fill( '(0, 0, 0)' ).join( ', ' );
+
+		}
+
+		const array = [];
+		const data = attribute.array;
+
+		for ( let i = 0; i < data.length; i += 3 ) {
+
+			array.push( `(${data[ i + 0 ].toPrecision( PRECISION )}, ${data[ i + 1 ].toPrecision( PRECISION )}, ${data[ i + 2 ].toPrecision( PRECISION )})` );
+
+		}
+
+		return array.join( ', ' );
+
+	}
+
+	function buildVector2Array( attribute, count ) {
+
+		if ( attribute === undefined ) {
+
+			console.warn( 'USDZExporter: UVs missing.' );
+			return Array( count ).fill( '(0, 0)' ).join( ', ' );
+
+		}
+
+		const array = [];
+		const data = attribute.array;
+
+		for ( let i = 0; i < data.length; i += 2 ) {
+
+			array.push( `(${data[ i + 0 ].toPrecision( PRECISION )}, ${1 - data[ i + 1 ].toPrecision( PRECISION )})` );
+
+		}
+
+		return array.join( ', ' );
+
+	} // Materials
+
+
+	function buildMaterials( materials ) {
+
+		const array = [];
+
+		for ( const uuid in materials ) {
+
+			const material = materials[ uuid ];
+			array.push( buildMaterial( material ) );
+
+		}
+
+		return `def "Materials"
+{
+${array.join( '' )}
+}
+
+`;
+
+	}
+
+	function buildMaterial( material ) {
+
+		// https://graphics.pixar.com/usd/docs/UsdPreviewSurface-Proposal.html
+		const pad = '						';
+		const parameters = [];
+
+		if ( material.map !== null ) {
+
+			parameters.push( `${pad}color3f inputs:diffuseColor.connect = </Textures/Texture_${material.map.id}.outputs:rgb>` );
+
+		} else {
+
+			parameters.push( `${pad}color3f inputs:diffuseColor = ${buildColor( material.color )}` );
+
+		}
+
+		if ( material.emissiveMap !== null ) {
+
+			parameters.push( `${pad}color3f inputs:emissiveColor.connect = </Textures/Texture_${material.emissiveMap.id}.outputs:rgb>` );
+
+		} else if ( material.emissive.getHex() > 0 ) {
+
+			parameters.push( `${pad}color3f inputs:emissiveColor = ${buildColor( material.emissive )}` );
+
+		}
+
+		if ( material.normalMap !== null ) {
+
+			parameters.push( `${pad}normal3f inputs:normal.connect = </Textures/Texture_${material.normalMap.id}.outputs:rgb>` );
+
+		}
+
+		if ( material.aoMap !== null ) {
+
+			parameters.push( `${pad}float inputs:occlusion.connect = </Textures/Texture_${material.aoMap.id}.outputs:r>` );
+
+		}
+
+		if ( material.roughnessMap !== null ) {
+
+			parameters.push( `${pad}float inputs:roughness.connect = </Textures/Texture_${material.roughnessMap.id}.outputs:g>` );
+
+		} else {
+
+			parameters.push( `${pad}float inputs:roughness = ${material.roughness}` );
+
+		}
+
+		if ( material.metalnessMap !== null ) {
+
+			parameters.push( `${pad}float inputs:metallic.connect = </Textures/Texture_${material.metalnessMap.id}.outputs:b>` );
+
+		} else {
+
+			parameters.push( `${pad}float inputs:metallic = ${material.metalness}` );
+
+		}
+
+		return `
+		def Material "Material_${material.id}"
+		{
+				token outputs:surface.connect = </Materials/Material_${material.id}/PreviewSurface.outputs:surface>
+
+				def Shader "PreviewSurface"
+				{
+						uniform token info:id = "UsdPreviewSurface"
+${parameters.join( '\n' )}
+						int inputs:useSpecularWorkflow = 0
+						token outputs:surface
+				}
+		}
+`;
+
+	}
+
+	function buildTextures( textures ) {
+
+		const array = [];
+
+		for ( const uuid in textures ) {
+
+			const texture = textures[ uuid ];
+			array.push( buildTexture( texture ) );
+
+		}
+
+		return `def "Textures"
+{
+${array.join( '' )}
+}
+
+`;
+
+	}
+
+	function buildTexture( texture ) {
+
+		return `
+		def Shader "Texture_${texture.id}"
+		{
+				uniform token info:id = "UsdUVTexture"
+				asset inputs:file = @textures/Texture_${texture.id}.jpg@
+				token inputs:wrapS = "repeat"
+				token inputs:wrapT = "repeat"
+				float outputs:r
+				float outputs:g
+				float outputs:b
+				float3 outputs:rgb
+		}
+`;
+
+	}
+
+	function buildColor( color ) {
+
+		return `(${color.r}, ${color.g}, ${color.b})`;
+
+	}
+
+	THREE.USDZExporter = USDZExporter;
+
+} )();

+ 46 - 47
examples/js/geometries/BoxLineGeometry.js

@@ -1,63 +1,62 @@
-THREE.BoxLineGeometry = function ( width, height, depth, widthSegments, heightSegments, depthSegments ) {
+( function () {
 
-	THREE.BufferGeometry.call( this );
+	var BoxLineGeometry = function ( width, height, depth, widthSegments, heightSegments, depthSegments ) {
 
-	width = width || 1;
-	height = height || 1;
-	depth = depth || 1;
+		THREE.BufferGeometry.call( this );
+		width = width || 1;
+		height = height || 1;
+		depth = depth || 1;
+		widthSegments = Math.floor( widthSegments ) || 1;
+		heightSegments = Math.floor( heightSegments ) || 1;
+		depthSegments = Math.floor( depthSegments ) || 1;
+		var widthHalf = width / 2;
+		var heightHalf = height / 2;
+		var depthHalf = depth / 2;
+		var segmentWidth = width / widthSegments;
+		var segmentHeight = height / heightSegments;
+		var segmentDepth = depth / depthSegments;
+		var vertices = [];
+		var x = - widthHalf,
+			y = - heightHalf,
+			z = - depthHalf;
 
-	widthSegments = Math.floor( widthSegments ) || 1;
-	heightSegments = Math.floor( heightSegments ) || 1;
-	depthSegments = Math.floor( depthSegments ) || 1;
+		for ( var i = 0; i <= widthSegments; i ++ ) {
 
-	var widthHalf = width / 2;
-	var heightHalf = height / 2;
-	var depthHalf = depth / 2;
+			vertices.push( x, - heightHalf, - depthHalf, x, heightHalf, - depthHalf );
+			vertices.push( x, heightHalf, - depthHalf, x, heightHalf, depthHalf );
+			vertices.push( x, heightHalf, depthHalf, x, - heightHalf, depthHalf );
+			vertices.push( x, - heightHalf, depthHalf, x, - heightHalf, - depthHalf );
+			x += segmentWidth;
 
-	var segmentWidth = width / widthSegments;
-	var segmentHeight = height / heightSegments;
-	var segmentDepth = depth / depthSegments;
+		}
 
-	var vertices = [];
+		for ( var i = 0; i <= heightSegments; i ++ ) {
 
-	var x = - widthHalf, y = - heightHalf, z = - depthHalf;
+			vertices.push( - widthHalf, y, - depthHalf, widthHalf, y, - depthHalf );
+			vertices.push( widthHalf, y, - depthHalf, widthHalf, y, depthHalf );
+			vertices.push( widthHalf, y, depthHalf, - widthHalf, y, depthHalf );
+			vertices.push( - widthHalf, y, depthHalf, - widthHalf, y, - depthHalf );
+			y += segmentHeight;
 
-	for ( var i = 0; i <= widthSegments; i ++ ) {
+		}
 
-		vertices.push( x, - heightHalf, - depthHalf, x, heightHalf, - depthHalf );
-		vertices.push( x, heightHalf, - depthHalf, x, heightHalf, depthHalf );
-		vertices.push( x, heightHalf, depthHalf, x, - heightHalf, depthHalf );
-		vertices.push( x, - heightHalf, depthHalf, x, - heightHalf, - depthHalf );
+		for ( var i = 0; i <= depthSegments; i ++ ) {
 
-		x += segmentWidth;
+			vertices.push( - widthHalf, - heightHalf, z, - widthHalf, heightHalf, z );
+			vertices.push( - widthHalf, heightHalf, z, widthHalf, heightHalf, z );
+			vertices.push( widthHalf, heightHalf, z, widthHalf, - heightHalf, z );
+			vertices.push( widthHalf, - heightHalf, z, - widthHalf, - heightHalf, z );
+			z += segmentDepth;
 
-	}
+		}
 
-	for ( var i = 0; i <= heightSegments; i ++ ) {
+		this.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
 
-		vertices.push( - widthHalf, y, - depthHalf, widthHalf, y, - depthHalf );
-		vertices.push( widthHalf, y, - depthHalf, widthHalf, y, depthHalf );
-		vertices.push( widthHalf, y, depthHalf, - widthHalf, y, depthHalf );
-		vertices.push( - widthHalf, y, depthHalf, - widthHalf, y, - depthHalf );
+	};
 
-		y += segmentHeight;
+	BoxLineGeometry.prototype = Object.create( THREE.BufferGeometry.prototype );
+	BoxLineGeometry.prototype.constructor = BoxLineGeometry;
 
-	}
+	THREE.BoxLineGeometry = BoxLineGeometry;
 
-	for ( var i = 0; i <= depthSegments; i ++ ) {
-
-		vertices.push( - widthHalf, - heightHalf, z, - widthHalf, heightHalf, z );
-		vertices.push( - widthHalf, heightHalf, z, widthHalf, heightHalf, z );
-		vertices.push( widthHalf, heightHalf, z, widthHalf, - heightHalf, z );
-		vertices.push( widthHalf, - heightHalf, z, - widthHalf, - heightHalf, z );
-
-		z += segmentDepth;
-
-	}
-
-	this.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
-
-};
-
-THREE.BoxLineGeometry.prototype = Object.create( THREE.BufferGeometry.prototype );
-THREE.BoxLineGeometry.prototype.constructor = THREE.BoxLineGeometry;
+} )();

+ 27 - 32
examples/js/geometries/ConvexGeometry.js

@@ -1,52 +1,47 @@
-// ConvexGeometry
+( function () {
 
-THREE.ConvexGeometry = function ( points ) {
+	var ConvexGeometry = function ( points ) {
 
-	THREE.BufferGeometry.call( this );
+		THREE.BufferGeometry.call( this ); // buffers
 
-	// buffers
+		var vertices = [];
+		var normals = [];
 
-	var vertices = [];
-	var normals = [];
+		if ( THREE.ConvexHull === undefined ) {
 
-	if ( THREE.ConvexHull === undefined ) {
+			console.error( 'THREE.ConvexBufferGeometry: ConvexBufferGeometry relies on THREE.ConvexHull' );
 
-		console.error( 'THREE.ConvexBufferGeometry: ConvexBufferGeometry relies on THREE.ConvexHull' );
+		}
 
-	}
+		var convexHull = new THREE.ConvexHull().setFromPoints( points ); // generate vertices and normals
 
-	var convexHull = new THREE.ConvexHull().setFromPoints( points );
+		var faces = convexHull.faces;
 
-	// generate vertices and normals
+		for ( var i = 0; i < faces.length; i ++ ) {
 
-	var faces = convexHull.faces;
+			var face = faces[ i ];
+			var edge = face.edge; // we move along a doubly-connected edge list to access all face points (see HalfEdge docs)
 
-	for ( var i = 0; i < faces.length; i ++ ) {
+			do {
 
-		var face = faces[ i ];
-		var edge = face.edge;
+				var point = edge.head().point;
+				vertices.push( point.x, point.y, point.z );
+				normals.push( face.normal.x, face.normal.y, face.normal.z );
+				edge = edge.next;
 
-		// we move along a doubly-connected edge list to access all face points (see HalfEdge docs)
+			} while ( edge !== face.edge );
 
-		do {
+		} // build geometry
 
-			var point = edge.head().point;
 
-			vertices.push( point.x, point.y, point.z );
-			normals.push( face.normal.x, face.normal.y, face.normal.z );
+		this.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
+		this.setAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) );
 
-			edge = edge.next;
+	};
 
-		} while ( edge !== face.edge );
+	ConvexGeometry.prototype = Object.create( THREE.BufferGeometry.prototype );
+	ConvexGeometry.prototype.constructor = ConvexGeometry;
 
-	}
+	THREE.ConvexGeometry = ConvexGeometry;
 
-	// build geometry
-
-	this.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
-	this.setAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) );
-
-};
-
-THREE.ConvexGeometry.prototype = Object.create( THREE.BufferGeometry.prototype );
-THREE.ConvexGeometry.prototype.constructor = THREE.ConvexGeometry;
+} )();

+ 179 - 234
examples/js/geometries/DecalGeometry.js

@@ -1,4 +1,6 @@
-/**
+( function () {
+
+	/**
  * You can use this geometry to create a decal mesh, that serves different kinds of purposes.
  * e.g. adding unique details to models, performing dynamic visual environmental changes or covering seams.
  *
@@ -13,336 +15,279 @@
  *
  */
 
-THREE.DecalGeometry = function ( mesh, position, orientation, size ) {
-
-	THREE.BufferGeometry.call( this );
-
-	// buffers
-
-	var vertices = [];
-	var normals = [];
-	var uvs = [];
-
-	// helpers
+	var DecalGeometry = function ( mesh, position, orientation, size ) {
 
-	var plane = new THREE.Vector3();
+		THREE.BufferGeometry.call( this ); // buffers
 
-	// this matrix represents the transformation of the decal projector
+		var vertices = [];
+		var normals = [];
+		var uvs = []; // helpers
 
-	var projectorMatrix = new THREE.Matrix4();
-	projectorMatrix.makeRotationFromEuler( orientation );
-	projectorMatrix.setPosition( position );
+		var plane = new THREE.Vector3(); // this matrix represents the transformation of the decal projector
 
-	var projectorMatrixInverse = new THREE.Matrix4();
-	projectorMatrixInverse.copy( projectorMatrix ).invert();
+		var projectorMatrix = new THREE.Matrix4();
+		projectorMatrix.makeRotationFromEuler( orientation );
+		projectorMatrix.setPosition( position );
+		var projectorMatrixInverse = new THREE.Matrix4();
+		projectorMatrixInverse.copy( projectorMatrix ).invert(); // generate buffers
 
-	// generate buffers
+		generate(); // build geometry
 
-	generate();
+		this.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
+		this.setAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) );
+		this.setAttribute( 'uv', new THREE.Float32BufferAttribute( uvs, 2 ) );
 
-	// build geometry
+		function generate() {
 
-	this.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
-	this.setAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) );
-	this.setAttribute( 'uv', new THREE.Float32BufferAttribute( uvs, 2 ) );
+			var i;
+			var decalVertices = [];
+			var vertex = new THREE.Vector3();
+			var normal = new THREE.Vector3(); // handle different geometry types
 
-	function generate() {
+			if ( mesh.geometry.isGeometry === true ) {
 
-		var i;
+				console.error( 'THREE.DecalGeometry no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
+				return;
 
-		var decalVertices = [];
-
-		var vertex = new THREE.Vector3();
-		var normal = new THREE.Vector3();
-
-		// handle different geometry types
+			}
 
-		if ( mesh.geometry.isGeometry === true ) {
+			var geometry = mesh.geometry;
+			var positionAttribute = geometry.attributes.position;
+			var normalAttribute = geometry.attributes.normal; // first, create an array of 'DecalVertex' objects
+			// three consecutive 'DecalVertex' objects represent a single face
+			//
+			// this data structure will be later used to perform the clipping
 
-			console.error( 'THREE.DecalGeometry no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
-			return;
+			if ( geometry.index !== null ) {
 
-		}
+				// indexed THREE.BufferGeometry
+				var index = geometry.index;
 
-		var geometry = mesh.geometry;
+				for ( i = 0; i < index.count; i ++ ) {
 
-		var positionAttribute = geometry.attributes.position;
-		var normalAttribute = geometry.attributes.normal;
+					vertex.fromBufferAttribute( positionAttribute, index.getX( i ) );
+					normal.fromBufferAttribute( normalAttribute, index.getX( i ) );
+					pushDecalVertex( decalVertices, vertex, normal );
 
-		// first, create an array of 'DecalVertex' objects
-		// three consecutive 'DecalVertex' objects represent a single face
-		//
-		// this data structure will be later used to perform the clipping
+				}
 
-		if ( geometry.index !== null ) {
+			} else {
 
-			// indexed BufferGeometry
+				// non-indexed THREE.BufferGeometry
+				for ( i = 0; i < positionAttribute.count; i ++ ) {
 
-			var index = geometry.index;
+					vertex.fromBufferAttribute( positionAttribute, i );
+					normal.fromBufferAttribute( normalAttribute, i );
+					pushDecalVertex( decalVertices, vertex, normal );
 
-			for ( i = 0; i < index.count; i ++ ) {
+				}
 
-				vertex.fromBufferAttribute( positionAttribute, index.getX( i ) );
-				normal.fromBufferAttribute( normalAttribute, index.getX( i ) );
+			} // second, clip the geometry so that it doesn't extend out from the projector
 
-				pushDecalVertex( decalVertices, vertex, normal );
 
-			}
+			decalVertices = clipGeometry( decalVertices, plane.set( 1, 0, 0 ) );
+			decalVertices = clipGeometry( decalVertices, plane.set( - 1, 0, 0 ) );
+			decalVertices = clipGeometry( decalVertices, plane.set( 0, 1, 0 ) );
+			decalVertices = clipGeometry( decalVertices, plane.set( 0, - 1, 0 ) );
+			decalVertices = clipGeometry( decalVertices, plane.set( 0, 0, 1 ) );
+			decalVertices = clipGeometry( decalVertices, plane.set( 0, 0, - 1 ) ); // third, generate final vertices, normals and uvs
 
-		} else {
+			for ( i = 0; i < decalVertices.length; i ++ ) {
 
-			// non-indexed BufferGeometry
+				var decalVertex = decalVertices[ i ]; // create texture coordinates (we are still in projector space)
 
-			for ( i = 0; i < positionAttribute.count; i ++ ) {
+				uvs.push( 0.5 + decalVertex.position.x / size.x, 0.5 + decalVertex.position.y / size.y ); // transform the vertex back to world space
 
-				vertex.fromBufferAttribute( positionAttribute, i );
-				normal.fromBufferAttribute( normalAttribute, i );
+				decalVertex.position.applyMatrix4( projectorMatrix ); // now create vertex and normal buffer data
 
-				pushDecalVertex( decalVertices, vertex, normal );
+				vertices.push( decalVertex.position.x, decalVertex.position.y, decalVertex.position.z );
+				normals.push( decalVertex.normal.x, decalVertex.normal.y, decalVertex.normal.z );
 
 			}
 
 		}
 
-		// second, clip the geometry so that it doesn't extend out from the projector
-
-		decalVertices = clipGeometry( decalVertices, plane.set( 1, 0, 0 ) );
-		decalVertices = clipGeometry( decalVertices, plane.set( - 1, 0, 0 ) );
-		decalVertices = clipGeometry( decalVertices, plane.set( 0, 1, 0 ) );
-		decalVertices = clipGeometry( decalVertices, plane.set( 0, - 1, 0 ) );
-		decalVertices = clipGeometry( decalVertices, plane.set( 0, 0, 1 ) );
-		decalVertices = clipGeometry( decalVertices, plane.set( 0, 0, - 1 ) );
-
-		// third, generate final vertices, normals and uvs
-
-		for ( i = 0; i < decalVertices.length; i ++ ) {
-
-			var decalVertex = decalVertices[ i ];
-
-			// create texture coordinates (we are still in projector space)
+		function pushDecalVertex( decalVertices, vertex, normal ) {
 
-			uvs.push(
-				0.5 + ( decalVertex.position.x / size.x ),
-				0.5 + ( decalVertex.position.y / size.y )
-			);
-
-			// transform the vertex back to world space
-
-			decalVertex.position.applyMatrix4( projectorMatrix );
-
-			// now create vertex and normal buffer data
-
-			vertices.push( decalVertex.position.x, decalVertex.position.y, decalVertex.position.z );
-			normals.push( decalVertex.normal.x, decalVertex.normal.y, decalVertex.normal.z );
+			// transform the vertex to world space, then to projector space
+			vertex.applyMatrix4( mesh.matrixWorld );
+			vertex.applyMatrix4( projectorMatrixInverse );
+			normal.transformDirection( mesh.matrixWorld );
+			decalVertices.push( new DecalVertex( vertex.clone(), normal.clone() ) );
 
 		}
 
-	}
-
-	function pushDecalVertex( decalVertices, vertex, normal ) {
-
-		// transform the vertex to world space, then to projector space
-
-		vertex.applyMatrix4( mesh.matrixWorld );
-		vertex.applyMatrix4( projectorMatrixInverse );
-
-		normal.transformDirection( mesh.matrixWorld );
+		function clipGeometry( inVertices, plane ) {
 
-		decalVertices.push( new THREE.DecalVertex( vertex.clone(), normal.clone() ) );
+			var outVertices = [];
+			var s = 0.5 * Math.abs( size.dot( plane ) ); // a single iteration clips one face,
+			// which consists of three consecutive 'DecalVertex' objects
 
-	}
+			for ( var i = 0; i < inVertices.length; i += 3 ) {
 
-	function clipGeometry( inVertices, plane ) {
+				var v1Out,
+					v2Out,
+					v3Out,
+					total = 0;
+				var nV1, nV2, nV3, nV4;
+				var d1 = inVertices[ i + 0 ].position.dot( plane ) - s;
+				var d2 = inVertices[ i + 1 ].position.dot( plane ) - s;
+				var d3 = inVertices[ i + 2 ].position.dot( plane ) - s;
+				v1Out = d1 > 0;
+				v2Out = d2 > 0;
+				v3Out = d3 > 0; // calculate, how many vertices of the face lie outside of the clipping plane
 
-		var outVertices = [];
+				total = ( v1Out ? 1 : 0 ) + ( v2Out ? 1 : 0 ) + ( v3Out ? 1 : 0 );
 
-		var s = 0.5 * Math.abs( size.dot( plane ) );
+				switch ( total ) {
 
-		// a single iteration clips one face,
-		// which consists of three consecutive 'DecalVertex' objects
+					case 0:
+					{
 
-		for ( var i = 0; i < inVertices.length; i += 3 ) {
-
-			var v1Out, v2Out, v3Out, total = 0;
-			var nV1, nV2, nV3, nV4;
-
-			var d1 = inVertices[ i + 0 ].position.dot( plane ) - s;
-			var d2 = inVertices[ i + 1 ].position.dot( plane ) - s;
-			var d3 = inVertices[ i + 2 ].position.dot( plane ) - s;
-
-			v1Out = d1 > 0;
-			v2Out = d2 > 0;
-			v3Out = d3 > 0;
-
-			// calculate, how many vertices of the face lie outside of the clipping plane
+						// the entire face lies inside of the plane, no clipping needed
+						outVertices.push( inVertices[ i ] );
+						outVertices.push( inVertices[ i + 1 ] );
+						outVertices.push( inVertices[ i + 2 ] );
+						break;
 
-			total = ( v1Out ? 1 : 0 ) + ( v2Out ? 1 : 0 ) + ( v3Out ? 1 : 0 );
+					}
 
-			switch ( total ) {
+					case 1:
+					{
 
-				case 0: {
+						// one vertex lies outside of the plane, perform clipping
+						if ( v1Out ) {
 
-					// the entire face lies inside of the plane, no clipping needed
+							nV1 = inVertices[ i + 1 ];
+							nV2 = inVertices[ i + 2 ];
+							nV3 = clip( inVertices[ i ], nV1, plane, s );
+							nV4 = clip( inVertices[ i ], nV2, plane, s );
 
-					outVertices.push( inVertices[ i ] );
-					outVertices.push( inVertices[ i + 1 ] );
-					outVertices.push( inVertices[ i + 2 ] );
-					break;
+						}
 
-				}
+						if ( v2Out ) {
 
-				case 1: {
+							nV1 = inVertices[ i ];
+							nV2 = inVertices[ i + 2 ];
+							nV3 = clip( inVertices[ i + 1 ], nV1, plane, s );
+							nV4 = clip( inVertices[ i + 1 ], nV2, plane, s );
+							outVertices.push( nV3 );
+							outVertices.push( nV2.clone() );
+							outVertices.push( nV1.clone() );
+							outVertices.push( nV2.clone() );
+							outVertices.push( nV3.clone() );
+							outVertices.push( nV4 );
+							break;
 
-					// one vertex lies outside of the plane, perform clipping
+						}
 
-					if ( v1Out ) {
+						if ( v3Out ) {
 
-						nV1 = inVertices[ i + 1 ];
-						nV2 = inVertices[ i + 2 ];
-						nV3 = clip( inVertices[ i ], nV1, plane, s );
-						nV4 = clip( inVertices[ i ], nV2, plane, s );
+							nV1 = inVertices[ i ];
+							nV2 = inVertices[ i + 1 ];
+							nV3 = clip( inVertices[ i + 2 ], nV1, plane, s );
+							nV4 = clip( inVertices[ i + 2 ], nV2, plane, s );
 
-					}
+						}
 
-					if ( v2Out ) {
-
-						nV1 = inVertices[ i ];
-						nV2 = inVertices[ i + 2 ];
-						nV3 = clip( inVertices[ i + 1 ], nV1, plane, s );
-						nV4 = clip( inVertices[ i + 1 ], nV2, plane, s );
-
-						outVertices.push( nV3 );
-						outVertices.push( nV2.clone() );
 						outVertices.push( nV1.clone() );
-
 						outVertices.push( nV2.clone() );
-						outVertices.push( nV3.clone() );
+						outVertices.push( nV3 );
 						outVertices.push( nV4 );
+						outVertices.push( nV3.clone() );
+						outVertices.push( nV2.clone() );
 						break;
 
 					}
 
-					if ( v3Out ) {
-
-						nV1 = inVertices[ i ];
-						nV2 = inVertices[ i + 1 ];
-						nV3 = clip( inVertices[ i + 2 ], nV1, plane, s );
-						nV4 = clip( inVertices[ i + 2 ], nV2, plane, s );
-
-					}
-
-					outVertices.push( nV1.clone() );
-					outVertices.push( nV2.clone() );
-					outVertices.push( nV3 );
+					case 2:
+					{
 
-					outVertices.push( nV4 );
-					outVertices.push( nV3.clone() );
-					outVertices.push( nV2.clone() );
+						// two vertices lies outside of the plane, perform clipping
+						if ( ! v1Out ) {
 
-					break;
+							nV1 = inVertices[ i ].clone();
+							nV2 = clip( nV1, inVertices[ i + 1 ], plane, s );
+							nV3 = clip( nV1, inVertices[ i + 2 ], plane, s );
+							outVertices.push( nV1 );
+							outVertices.push( nV2 );
+							outVertices.push( nV3 );
 
-				}
+						}
 
-				case 2: {
+						if ( ! v2Out ) {
 
-					// two vertices lies outside of the plane, perform clipping
+							nV1 = inVertices[ i + 1 ].clone();
+							nV2 = clip( nV1, inVertices[ i + 2 ], plane, s );
+							nV3 = clip( nV1, inVertices[ i ], plane, s );
+							outVertices.push( nV1 );
+							outVertices.push( nV2 );
+							outVertices.push( nV3 );
 
-					if ( ! v1Out ) {
+						}
 
-						nV1 = inVertices[ i ].clone();
-						nV2 = clip( nV1, inVertices[ i + 1 ], plane, s );
-						nV3 = clip( nV1, inVertices[ i + 2 ], plane, s );
-						outVertices.push( nV1 );
-						outVertices.push( nV2 );
-						outVertices.push( nV3 );
+						if ( ! v3Out ) {
 
-					}
+							nV1 = inVertices[ i + 2 ].clone();
+							nV2 = clip( nV1, inVertices[ i ], plane, s );
+							nV3 = clip( nV1, inVertices[ i + 1 ], plane, s );
+							outVertices.push( nV1 );
+							outVertices.push( nV2 );
+							outVertices.push( nV3 );
 
-					if ( ! v2Out ) {
+						}
 
-						nV1 = inVertices[ i + 1 ].clone();
-						nV2 = clip( nV1, inVertices[ i + 2 ], plane, s );
-						nV3 = clip( nV1, inVertices[ i ], plane, s );
-						outVertices.push( nV1 );
-						outVertices.push( nV2 );
-						outVertices.push( nV3 );
+						break;
 
 					}
 
-					if ( ! v3Out ) {
+					case 3:
+					{
 
-						nV1 = inVertices[ i + 2 ].clone();
-						nV2 = clip( nV1, inVertices[ i ], plane, s );
-						nV3 = clip( nV1, inVertices[ i + 1 ], plane, s );
-						outVertices.push( nV1 );
-						outVertices.push( nV2 );
-						outVertices.push( nV3 );
+						// the entire face lies outside of the plane, so let's discard the corresponding vertices
+						break;
 
 					}
 
-					break;
-
-				}
-
-				case 3: {
-
-					// the entire face lies outside of the plane, so let's discard the corresponding vertices
-
-					break;
-
 				}
 
 			}
 
-		}
-
-		return outVertices;
+			return outVertices;
 
-	}
-
-	function clip( v0, v1, p, s ) {
-
-		var d0 = v0.position.dot( p ) - s;
-		var d1 = v1.position.dot( p ) - s;
+		}
 
-		var s0 = d0 / ( d0 - d1 );
+		function clip( v0, v1, p, s ) {
 
-		var v = new THREE.DecalVertex(
-			new THREE.Vector3(
-				v0.position.x + s0 * ( v1.position.x - v0.position.x ),
-				v0.position.y + s0 * ( v1.position.y - v0.position.y ),
-				v0.position.z + s0 * ( v1.position.z - v0.position.z )
-			),
-			new THREE.Vector3(
-				v0.normal.x + s0 * ( v1.normal.x - v0.normal.x ),
-				v0.normal.y + s0 * ( v1.normal.y - v0.normal.y ),
-				v0.normal.z + s0 * ( v1.normal.z - v0.normal.z )
-			)
-		);
+			var d0 = v0.position.dot( p ) - s;
+			var d1 = v1.position.dot( p ) - s;
+			var s0 = d0 / ( d0 - d1 );
+			var v = new DecalVertex( new THREE.Vector3( v0.position.x + s0 * ( v1.position.x - v0.position.x ), v0.position.y + s0 * ( v1.position.y - v0.position.y ), v0.position.z + s0 * ( v1.position.z - v0.position.z ) ), new THREE.Vector3( v0.normal.x + s0 * ( v1.normal.x - v0.normal.x ), v0.normal.y + s0 * ( v1.normal.y - v0.normal.y ), v0.normal.z + s0 * ( v1.normal.z - v0.normal.z ) ) ); // need to clip more values (texture coordinates)? do it this way:
+			// intersectpoint.value = a.value + s * ( b.value - a.value );
 
-		// need to clip more values (texture coordinates)? do it this way:
-		// intersectpoint.value = a.value + s * ( b.value - a.value );
+			return v;
 
-		return v;
+		}
 
-	}
+	};
 
-};
+	DecalGeometry.prototype = Object.create( THREE.BufferGeometry.prototype );
+	DecalGeometry.prototype.constructor = DecalGeometry; // helper
 
-THREE.DecalGeometry.prototype = Object.create( THREE.BufferGeometry.prototype );
-THREE.DecalGeometry.prototype.constructor = THREE.DecalGeometry;
+	var DecalVertex = function ( position, normal ) {
 
-// helper
+		this.position = position;
+		this.normal = normal;
 
-THREE.DecalVertex = function ( position, normal ) {
+	};
 
-	this.position = position;
-	this.normal = normal;
+	DecalVertex.prototype.clone = function () {
 
-};
+		return new this.constructor( this.position.clone(), this.normal.clone() );
 
-THREE.DecalVertex.prototype.clone = function () {
+	};
 
-	return new this.constructor( this.position.clone(), this.normal.clone() );
+	THREE.DecalGeometry = DecalGeometry;
+	THREE.DecalVertex = DecalVertex;
 
-};
+} )();

+ 563 - 693
examples/js/geometries/LightningStrike.js

@@ -1,10 +1,12 @@
-/**
+( function () {
+
+	/**
  * @fileoverview LightningStrike object for creating lightning strikes and voltaic arcs.
  *
  *
  * Usage
  *
- * var myRay = new THREE.LightningStrike( paramsObject );
+ * var myRay = new LightningStrike( paramsObject );
  * var myRayMesh = new THREE.Mesh( myRay, myMaterial );
  * scene.add( myRayMesh );
  * ...
@@ -84,7 +86,7 @@
  *
  * @param {boolean} generateUVs If true, the ray geometry will have uv coordinates generated. u runs along the ray, and v across its perimeter. Default: false.
  *
- * @param {Object} randomGenerator Set here your random number generator which will seed the SimplexNoise and other decisions during ray tree creation.
+ * @param {Object} randomGenerator Set here your random number generator which will seed the THREE.SimplexNoise and other decisions during ray tree creation.
  * It can be used to generate repeatable rays. For that, set also the noiseSeed parameter, and each ray created with that generator and seed pair will be identical in time.
  * The randomGenerator parameter should be an object with a random() function similar to Math.random, but seedable.
  * It must have also a getSeed() method, which returns the current seed, and a setSeed( seed ) method, which accepts as seed a fractional number from 0 to 1, as well as any other number.
@@ -99,908 +101,776 @@
  *
 */
 
-THREE.LightningStrike = function ( rayParameters ) {
-
-	THREE.BufferGeometry.call( this );
-
-	this.type = 'LightningStrike';
-
-	// Set parameters, and set undefined parameters to default values
-	rayParameters = rayParameters || {};
-	this.init( THREE.LightningStrike.copyParameters( rayParameters, rayParameters ) );
-
-	// Creates and populates the mesh
-	this.createMesh();
-
-};
-
-THREE.LightningStrike.prototype = Object.create( THREE.BufferGeometry.prototype );
-
-THREE.LightningStrike.prototype.constructor = THREE.LightningStrike;
-
-THREE.LightningStrike.prototype.isLightningStrike = true;
-
-// Ray states
-THREE.LightningStrike.RAY_INITIALIZED = 0;
-THREE.LightningStrike.RAY_UNBORN = 1;
-THREE.LightningStrike.RAY_PROPAGATING = 2;
-THREE.LightningStrike.RAY_STEADY = 3;
-THREE.LightningStrike.RAY_VANISHING = 4;
-THREE.LightningStrike.RAY_EXTINGUISHED = 5;
-
-THREE.LightningStrike.COS30DEG = Math.cos( 30 * Math.PI / 180 );
-THREE.LightningStrike.SIN30DEG = Math.sin( 30 * Math.PI / 180 );
+	var LightningStrike = function ( rayParameters ) {
 
-THREE.LightningStrike.createRandomGenerator = function () {
+		THREE.BufferGeometry.call( this );
+		this.type = 'LightningStrike'; // Set parameters, and set undefined parameters to default values
 
-	var numSeeds = 2053;
-	var seeds = [];
+		rayParameters = rayParameters || {};
+		this.init( LightningStrike.copyParameters( rayParameters, rayParameters ) ); // Creates and populates the mesh
 
-	for ( var i = 0; i < numSeeds; i ++ ) {
+		this.createMesh();
 
-		seeds.push( Math.random() );
-
-	}
-
-	var generator = {
-
-		currentSeed: 0,
-
-		random: function () {
-
-			var value = seeds[ generator.currentSeed ];
-
-			generator.currentSeed = ( generator.currentSeed + 1 ) % numSeeds;
-
-			return value;
+	};
 
-		},
+	LightningStrike.prototype = Object.create( THREE.BufferGeometry.prototype );
+	LightningStrike.prototype.constructor = LightningStrike;
+	LightningStrike.prototype.isLightningStrike = true; // Ray states
 
-		getSeed: function () {
+	LightningStrike.RAY_INITIALIZED = 0;
+	LightningStrike.RAY_UNBORN = 1;
+	LightningStrike.RAY_PROPAGATING = 2;
+	LightningStrike.RAY_STEADY = 3;
+	LightningStrike.RAY_VANISHING = 4;
+	LightningStrike.RAY_EXTINGUISHED = 5;
+	LightningStrike.COS30DEG = Math.cos( 30 * Math.PI / 180 );
+	LightningStrike.SIN30DEG = Math.sin( 30 * Math.PI / 180 );
 
-			return generator.currentSeed / numSeeds;
+	LightningStrike.createRandomGenerator = function () {
 
-		},
+		var numSeeds = 2053;
+		var seeds = [];
 
-		setSeed: function ( seed ) {
+		for ( var i = 0; i < numSeeds; i ++ ) {
 
-			generator.currentSeed = Math.floor( seed * numSeeds ) % numSeeds;
+			seeds.push( Math.random() );
 
 		}
 
-	};
-
-	return generator;
-
-};
+		var generator = {
+			currentSeed: 0,
+			random: function () {
 
-THREE.LightningStrike.copyParameters = function ( dest, source ) {
+				var value = seeds[ generator.currentSeed ];
+				generator.currentSeed = ( generator.currentSeed + 1 ) % numSeeds;
+				return value;
 
-	source = source || {};
-	dest = dest || {};
+			},
+			getSeed: function () {
 
-	var vecCopy = function ( v ) {
+				return generator.currentSeed / numSeeds;
 
-		if ( source === dest ) {
+			},
+			setSeed: function ( seed ) {
 
-			return v;
+				generator.currentSeed = Math.floor( seed * numSeeds ) % numSeeds;
 
-		} else {
-
-			return v.clone();
-
-		}
+			}
+		};
+		return generator;
 
 	};
 
-	dest.sourceOffset = source.sourceOffset !== undefined ? vecCopy( source.sourceOffset ) : new THREE.Vector3( 0, 100, 0 ),
-	dest.destOffset = source.destOffset !== undefined ? vecCopy( source.destOffset ) : new THREE.Vector3( 0, 0, 0 ),
-
-	dest.timeScale = source.timeScale !== undefined ? source.timeScale : 1,
-	dest.roughness = source.roughness !== undefined ? source.roughness : 0.9,
-	dest.straightness = source.straightness !== undefined ? source.straightness : 0.7,
+	LightningStrike.copyParameters = function ( dest, source ) {
 
-	dest.up0 = source.up0 !== undefined ? vecCopy( source.up0 ) : new THREE.Vector3( 0, 0, 1 );
-	dest.up1 = source.up1 !== undefined ? vecCopy( source.up1 ) : new THREE.Vector3( 0, 0, 1 ),
-	dest.radius0 = source.radius0 !== undefined ? source.radius0 : 1,
-	dest.radius1 = source.radius1 !== undefined ? source.radius1 : 1,
-	dest.radius0Factor = source.radius0Factor !== undefined ? source.radius0Factor : 0.5,
-	dest.radius1Factor = source.radius1Factor !== undefined ? source.radius1Factor : 0.2,
-	dest.minRadius = source.minRadius !== undefined ? source.minRadius : 0.2,
+		source = source || {};
+		dest = dest || {};
 
-	// These parameters should not be changed after lightning creation. They can be changed but the ray will change its form abruptly:
+		var vecCopy = function ( v ) {
 
-	dest.isEternal = source.isEternal !== undefined ? source.isEternal : ( source.birthTime === undefined || source.deathTime === undefined ),
-	dest.birthTime = source.birthTime,
-	dest.deathTime = source.deathTime,
-	dest.propagationTimeFactor = source.propagationTimeFactor !== undefined ? source.propagationTimeFactor : 0.1,
-	dest.vanishingTimeFactor = source.vanishingTimeFactor !== undefined ? source.vanishingTimeFactor : 0.9,
-	dest.subrayPeriod = source.subrayPeriod !== undefined ? source.subrayPeriod : 4,
-	dest.subrayDutyCycle = source.subrayDutyCycle !== undefined ? source.subrayDutyCycle : 0.6;
+			if ( source === dest ) {
 
-	// These parameters cannot change after lightning creation:
+				return v;
 
-	dest.maxIterations = source.maxIterations !== undefined ? source.maxIterations : 9;
-	dest.isStatic = source.isStatic !== undefined ? source.isStatic : false;
-	dest.ramification = source.ramification !== undefined ? source.ramification : 5;
-	dest.maxSubrayRecursion = source.maxSubrayRecursion !== undefined ? source.maxSubrayRecursion : 3;
-	dest.recursionProbability = source.recursionProbability !== undefined ? source.recursionProbability : 0.6;
-	dest.generateUVs = source.generateUVs !== undefined ? source.generateUVs : false;
-	dest.randomGenerator = source.randomGenerator,
-	dest.noiseSeed = source.noiseSeed,
-	dest.onDecideSubrayCreation = source.onDecideSubrayCreation,
-	dest.onSubrayCreation = source.onSubrayCreation;
+			} else {
 
-	return dest;
+				return v.clone();
 
-};
+			}
 
-THREE.LightningStrike.prototype.update = function ( time ) {
+		};
 
-	if ( this.isStatic ) return;
+		dest.sourceOffset = source.sourceOffset !== undefined ? vecCopy( source.sourceOffset ) : new THREE.Vector3( 0, 100, 0 ), dest.destOffset = source.destOffset !== undefined ? vecCopy( source.destOffset ) : new THREE.Vector3( 0, 0, 0 ), dest.timeScale = source.timeScale !== undefined ? source.timeScale : 1, dest.roughness = source.roughness !== undefined ? source.roughness : 0.9, dest.straightness = source.straightness !== undefined ? source.straightness : 0.7, dest.up0 = source.up0 !== undefined ? vecCopy( source.up0 ) : new THREE.Vector3( 0, 0, 1 );
+		dest.up1 = source.up1 !== undefined ? vecCopy( source.up1 ) : new THREE.Vector3( 0, 0, 1 ), dest.radius0 = source.radius0 !== undefined ? source.radius0 : 1, dest.radius1 = source.radius1 !== undefined ? source.radius1 : 1, dest.radius0Factor = source.radius0Factor !== undefined ? source.radius0Factor : 0.5, dest.radius1Factor = source.radius1Factor !== undefined ? source.radius1Factor : 0.2, dest.minRadius = source.minRadius !== undefined ? source.minRadius : 0.2, // These parameters should not be changed after lightning creation. They can be changed but the ray will change its form abruptly:
+		dest.isEternal = source.isEternal !== undefined ? source.isEternal : source.birthTime === undefined || source.deathTime === undefined, dest.birthTime = source.birthTime, dest.deathTime = source.deathTime, dest.propagationTimeFactor = source.propagationTimeFactor !== undefined ? source.propagationTimeFactor : 0.1, dest.vanishingTimeFactor = source.vanishingTimeFactor !== undefined ? source.vanishingTimeFactor : 0.9, dest.subrayPeriod = source.subrayPeriod !== undefined ? source.subrayPeriod : 4, dest.subrayDutyCycle = source.subrayDutyCycle !== undefined ? source.subrayDutyCycle : 0.6; // These parameters cannot change after lightning creation:
 
-	if ( this.rayParameters.isEternal || ( this.rayParameters.birthTime <= time && time <= this.rayParameters.deathTime ) ) {
+		dest.maxIterations = source.maxIterations !== undefined ? source.maxIterations : 9;
+		dest.isStatic = source.isStatic !== undefined ? source.isStatic : false;
+		dest.ramification = source.ramification !== undefined ? source.ramification : 5;
+		dest.maxSubrayRecursion = source.maxSubrayRecursion !== undefined ? source.maxSubrayRecursion : 3;
+		dest.recursionProbability = source.recursionProbability !== undefined ? source.recursionProbability : 0.6;
+		dest.generateUVs = source.generateUVs !== undefined ? source.generateUVs : false;
+		dest.randomGenerator = source.randomGenerator, dest.noiseSeed = source.noiseSeed, dest.onDecideSubrayCreation = source.onDecideSubrayCreation, dest.onSubrayCreation = source.onSubrayCreation;
+		return dest;
 
-		this.updateMesh( time );
+	};
 
-		if ( time < this.subrays[ 0 ].endPropagationTime ) {
+	LightningStrike.prototype.update = function ( time ) {
 
-			this.state = THREE.LightningStrike.RAY_PROPAGATING;
+		if ( this.isStatic ) return;
 
-		} else if ( time > this.subrays[ 0 ].beginVanishingTime ) {
+		if ( this.rayParameters.isEternal || this.rayParameters.birthTime <= time && time <= this.rayParameters.deathTime ) {
 
-			this.state = THREE.LightningStrike.RAY_VANISHING;
+			this.updateMesh( time );
 
-		} else {
+			if ( time < this.subrays[ 0 ].endPropagationTime ) {
 
-			this.state = THREE.LightningStrike.RAY_STEADY;
+				this.state = LightningStrike.RAY_PROPAGATING;
 
-		}
+			} else if ( time > this.subrays[ 0 ].beginVanishingTime ) {
 
-		this.visible = true;
+				this.state = LightningStrike.RAY_VANISHING;
 
-	} else {
+			} else {
 
-		this.visible = false;
+				this.state = LightningStrike.RAY_STEADY;
 
-		if ( time < this.rayParameters.birthTime ) {
+			}
 
-			this.state = THREE.LightningStrike.RAY_UNBORN;
+			this.visible = true;
 
 		} else {
 
-			this.state = THREE.LightningStrike.RAY_EXTINGUISHED;
+			this.visible = false;
 
-		}
-
-	}
-
-};
-
-THREE.LightningStrike.prototype.init = function ( rayParameters ) {
-
-	// Init all the state from the parameters
-
-	this.rayParameters = rayParameters;
+			if ( time < this.rayParameters.birthTime ) {
 
-	// These parameters cannot change after lightning creation:
+				this.state = LightningStrike.RAY_UNBORN;
 
-	this.maxIterations = rayParameters.maxIterations !== undefined ? Math.floor( rayParameters.maxIterations ) : 9;
-	rayParameters.maxIterations = this.maxIterations;
-	this.isStatic = rayParameters.isStatic !== undefined ? rayParameters.isStatic : false;
-	rayParameters.isStatic = this.isStatic;
-	this.ramification = rayParameters.ramification !== undefined ? Math.floor( rayParameters.ramification ) : 5;
-	rayParameters.ramification = this.ramification;
-	this.maxSubrayRecursion = rayParameters.maxSubrayRecursion !== undefined ? Math.floor( rayParameters.maxSubrayRecursion ) : 3;
-	rayParameters.maxSubrayRecursion = this.maxSubrayRecursion;
-	this.recursionProbability = rayParameters.recursionProbability !== undefined ? rayParameters.recursionProbability : 0.6;
-	rayParameters.recursionProbability = this.recursionProbability;
-	this.generateUVs = rayParameters.generateUVs !== undefined ? rayParameters.generateUVs : false;
-	rayParameters.generateUVs = this.generateUVs;
+			} else {
 
-	// Random generator
-	if ( rayParameters.randomGenerator !== undefined ) {
+				this.state = LightningStrike.RAY_EXTINGUISHED;
 
-		this.randomGenerator = rayParameters.randomGenerator;
-		this.seedGenerator = rayParameters.randomGenerator;
-
-		if ( rayParameters.noiseSeed !== undefined ) {
-
-			this.seedGenerator.setSeed( rayParameters.noiseSeed );
+			}
 
 		}
 
-	} else {
-
-		this.randomGenerator = THREE.LightningStrike.createRandomGenerator();
-		this.seedGenerator = Math;
-
-	}
+	};
 
-	// Ray creation callbacks
-	if ( rayParameters.onDecideSubrayCreation !== undefined ) {
+	LightningStrike.prototype.init = function ( rayParameters ) {
 
-		this.onDecideSubrayCreation = rayParameters.onDecideSubrayCreation;
+		// Init all the state from the parameters
+		this.rayParameters = rayParameters; // These parameters cannot change after lightning creation:
 
-	} else {
+		this.maxIterations = rayParameters.maxIterations !== undefined ? Math.floor( rayParameters.maxIterations ) : 9;
+		rayParameters.maxIterations = this.maxIterations;
+		this.isStatic = rayParameters.isStatic !== undefined ? rayParameters.isStatic : false;
+		rayParameters.isStatic = this.isStatic;
+		this.ramification = rayParameters.ramification !== undefined ? Math.floor( rayParameters.ramification ) : 5;
+		rayParameters.ramification = this.ramification;
+		this.maxSubrayRecursion = rayParameters.maxSubrayRecursion !== undefined ? Math.floor( rayParameters.maxSubrayRecursion ) : 3;
+		rayParameters.maxSubrayRecursion = this.maxSubrayRecursion;
+		this.recursionProbability = rayParameters.recursionProbability !== undefined ? rayParameters.recursionProbability : 0.6;
+		rayParameters.recursionProbability = this.recursionProbability;
+		this.generateUVs = rayParameters.generateUVs !== undefined ? rayParameters.generateUVs : false;
+		rayParameters.generateUVs = this.generateUVs; // Random generator
 
-		this.createDefaultSubrayCreationCallbacks();
+		if ( rayParameters.randomGenerator !== undefined ) {
 
-		if ( rayParameters.onSubrayCreation !== undefined ) {
+			this.randomGenerator = rayParameters.randomGenerator;
+			this.seedGenerator = rayParameters.randomGenerator;
 
-			this.onSubrayCreation = rayParameters.onSubrayCreation;
+			if ( rayParameters.noiseSeed !== undefined ) {
 
-		}
+				this.seedGenerator.setSeed( rayParameters.noiseSeed );
 
-	}
+			}
 
-	// Internal state
+		} else {
 
-	this.state = THREE.LightningStrike.RAY_INITIALIZED;
+			this.randomGenerator = LightningStrike.createRandomGenerator();
+			this.seedGenerator = Math;
 
-	this.maxSubrays = Math.ceil( 1 + Math.pow( this.ramification, Math.max( 0, this.maxSubrayRecursion - 1 ) ) );
-	rayParameters.maxSubrays = this.maxSubrays;
+		} // Ray creation callbacks
 
-	this.maxRaySegments = 2 * ( 1 << this.maxIterations );
 
-	this.subrays = [];
+		if ( rayParameters.onDecideSubrayCreation !== undefined ) {
 
-	for ( var i = 0; i < this.maxSubrays; i ++ ) {
+			this.onDecideSubrayCreation = rayParameters.onDecideSubrayCreation;
 
-		this.subrays.push( this.createSubray() );
+		} else {
 
-	}
+			this.createDefaultSubrayCreationCallbacks();
 
-	this.raySegments = [];
+			if ( rayParameters.onSubrayCreation !== undefined ) {
 
-	for ( var i = 0; i < this.maxRaySegments; i ++ ) {
+				this.onSubrayCreation = rayParameters.onSubrayCreation;
 
-		this.raySegments.push( this.createSegment() );
+			}
 
-	}
+		} // Internal state
 
-	this.time = 0;
-	this.timeFraction = 0;
-	this.currentSegmentCallback = null;
-	this.currentCreateTriangleVertices = this.generateUVs ? this.createTriangleVerticesWithUVs : this.createTriangleVerticesWithoutUVs;
-	this.numSubrays = 0;
-	this.currentSubray = null;
-	this.currentSegmentIndex = 0;
-	this.isInitialSegment = false;
-	this.subrayProbability = 0;
 
-	this.currentVertex = 0;
-	this.currentIndex = 0;
-	this.currentCoordinate = 0;
-	this.currentUVCoordinate = 0;
-	this.vertices = null;
-	this.uvs = null;
-	this.indices = null;
-	this.positionAttribute = null;
-	this.uvsAttribute = null;
+		this.state = LightningStrike.RAY_INITIALIZED;
+		this.maxSubrays = Math.ceil( 1 + Math.pow( this.ramification, Math.max( 0, this.maxSubrayRecursion - 1 ) ) );
+		rayParameters.maxSubrays = this.maxSubrays;
+		this.maxRaySegments = 2 * ( 1 << this.maxIterations );
+		this.subrays = [];
 
-	this.simplexX = new THREE.SimplexNoise( this.seedGenerator );
-	this.simplexY = new THREE.SimplexNoise( this.seedGenerator );
-	this.simplexZ = new THREE.SimplexNoise( this.seedGenerator );
+		for ( var i = 0; i < this.maxSubrays; i ++ ) {
 
-	// Temp vectors
-	this.forwards = new THREE.Vector3();
-	this.forwardsFill = new THREE.Vector3();
-	this.side = new THREE.Vector3();
-	this.down = new THREE.Vector3();
-	this.middlePos = new THREE.Vector3();
-	this.middleLinPos = new THREE.Vector3();
-	this.newPos = new THREE.Vector3();
-	this.vPos = new THREE.Vector3();
-	this.cross1 = new THREE.Vector3();
+			this.subrays.push( this.createSubray() );
 
-};
+		}
 
-THREE.LightningStrike.prototype.createMesh = function () {
+		this.raySegments = [];
 
-	var maxDrawableSegmentsPerSubRay = 1 << this.maxIterations;
+		for ( var i = 0; i < this.maxRaySegments; i ++ ) {
 
-	var maxVerts = 3 * ( maxDrawableSegmentsPerSubRay + 1 ) * this.maxSubrays;
-	var maxIndices = 18 * maxDrawableSegmentsPerSubRay * this.maxSubrays;
+			this.raySegments.push( this.createSegment() );
 
-	this.vertices = new Float32Array( maxVerts * 3 );
-	this.indices = new Uint32Array( maxIndices );
-	if ( this.generateUVs ) {
+		}
 
-		this.uvs = new Float32Array( maxVerts * 2 );
+		this.time = 0;
+		this.timeFraction = 0;
+		this.currentSegmentCallback = null;
+		this.currentCreateTriangleVertices = this.generateUVs ? this.createTriangleVerticesWithUVs : this.createTriangleVerticesWithoutUVs;
+		this.numSubrays = 0;
+		this.currentSubray = null;
+		this.currentSegmentIndex = 0;
+		this.isInitialSegment = false;
+		this.subrayProbability = 0;
+		this.currentVertex = 0;
+		this.currentIndex = 0;
+		this.currentCoordinate = 0;
+		this.currentUVCoordinate = 0;
+		this.vertices = null;
+		this.uvs = null;
+		this.indices = null;
+		this.positionAttribute = null;
+		this.uvsAttribute = null;
+		this.simplexX = new THREE.SimplexNoise( this.seedGenerator );
+		this.simplexY = new THREE.SimplexNoise( this.seedGenerator );
+		this.simplexZ = new THREE.SimplexNoise( this.seedGenerator ); // Temp vectors
+
+		this.forwards = new THREE.Vector3();
+		this.forwardsFill = new THREE.Vector3();
+		this.side = new THREE.Vector3();
+		this.down = new THREE.Vector3();
+		this.middlePos = new THREE.Vector3();
+		this.middleLinPos = new THREE.Vector3();
+		this.newPos = new THREE.Vector3();
+		this.vPos = new THREE.Vector3();
+		this.cross1 = new THREE.Vector3();
 
-	}
+	};
 
-	// Populate the mesh
-	this.fillMesh( 0 );
+	LightningStrike.prototype.createMesh = function () {
 
-	this.setIndex( new THREE.Uint32BufferAttribute( this.indices, 1 ) );
+		var maxDrawableSegmentsPerSubRay = 1 << this.maxIterations;
+		var maxVerts = 3 * ( maxDrawableSegmentsPerSubRay + 1 ) * this.maxSubrays;
+		var maxIndices = 18 * maxDrawableSegmentsPerSubRay * this.maxSubrays;
+		this.vertices = new Float32Array( maxVerts * 3 );
+		this.indices = new Uint32Array( maxIndices );
 
-	this.positionAttribute = new THREE.Float32BufferAttribute( this.vertices, 3 );
-	this.setAttribute( 'position', this.positionAttribute );
+		if ( this.generateUVs ) {
 
-	if ( this.generateUVs ) {
+			this.uvs = new Float32Array( maxVerts * 2 );
 
-		this.uvsAttribute = new THREE.Float32BufferAttribute( new Float32Array( this.uvs ), 2 );
-		this.setAttribute( 'uv', this.uvsAttribute );
+		} // Populate the mesh
 
-	}
 
-	if ( ! this.isStatic ) {
+		this.fillMesh( 0 );
+		this.setIndex( new THREE.Uint32BufferAttribute( this.indices, 1 ) );
+		this.positionAttribute = new THREE.Float32BufferAttribute( this.vertices, 3 );
+		this.setAttribute( 'position', this.positionAttribute );
 
-		this.index.usage = THREE.DynamicDrawUsage;
-		this.positionAttribute.usage = THREE.DynamicDrawUsage;
 		if ( this.generateUVs ) {
 
-			this.uvsAttribute.usage = THREE.DynamicDrawUsage;
+			this.uvsAttribute = new THREE.Float32BufferAttribute( new Float32Array( this.uvs ), 2 );
+			this.setAttribute( 'uv', this.uvsAttribute );
 
 		}
 
-	}
-
-	// Store buffers for later modification
-	this.vertices = this.positionAttribute.array;
-	this.indices = this.index.array;
-	if ( this.generateUVs ) {
-
-		this.uvs = this.uvsAttribute.array;
+		if ( ! this.isStatic ) {
 
-	}
+			this.index.usage = THREE.DynamicDrawUsage;
+			this.positionAttribute.usage = THREE.DynamicDrawUsage;
 
-};
+			if ( this.generateUVs ) {
 
-THREE.LightningStrike.prototype.updateMesh = function ( time ) {
+				this.uvsAttribute.usage = THREE.DynamicDrawUsage;
 
-	this.fillMesh( time );
-
-	this.drawRange.count = this.currentIndex;
-
-	this.index.needsUpdate = true;
+			}
 
-	this.positionAttribute.needsUpdate = true;
+		} // Store buffers for later modification
 
-	if ( this.generateUVs ) {
 
-		this.uvsAttribute.needsUpdate = true;
+		this.vertices = this.positionAttribute.array;
+		this.indices = this.index.array;
 
-	}
+		if ( this.generateUVs ) {
 
-};
+			this.uvs = this.uvsAttribute.array;
 
-THREE.LightningStrike.prototype.fillMesh = function ( time ) {
+		}
 
-	var scope = this;
+	};
 
-	this.currentVertex = 0;
-	this.currentIndex = 0;
-	this.currentCoordinate = 0;
-	this.currentUVCoordinate = 0;
+	LightningStrike.prototype.updateMesh = function ( time ) {
 
-	this.fractalRay( time, function fillVertices( segment ) {
+		this.fillMesh( time );
+		this.drawRange.count = this.currentIndex;
+		this.index.needsUpdate = true;
+		this.positionAttribute.needsUpdate = true;
 
-		var subray = scope.currentSubray;
+		if ( this.generateUVs ) {
 
-		if ( time < subray.birthTime ) { //&& ( ! this.rayParameters.isEternal || scope.currentSubray.recursion > 0 ) ) {
+			this.uvsAttribute.needsUpdate = true;
 
-			return;
+		}
 
-		} else if ( this.rayParameters.isEternal && scope.currentSubray.recursion == 0 ) {
+	};
 
-			// Eternal rays don't propagate nor vanish, but its subrays do
+	LightningStrike.prototype.fillMesh = function ( time ) {
 
-			scope.createPrism( segment );
+		var scope = this;
+		this.currentVertex = 0;
+		this.currentIndex = 0;
+		this.currentCoordinate = 0;
+		this.currentUVCoordinate = 0;
+		this.fractalRay( time, function fillVertices( segment ) {
 
-			scope.onDecideSubrayCreation( segment, scope );
+			var subray = scope.currentSubray;
 
-		} else if ( time < subray.endPropagationTime ) {
+			if ( time < subray.birthTime ) {
 
-			if ( scope.timeFraction >= segment.fraction0 * subray.propagationTimeFactor ) {
+				//&& ( ! this.rayParameters.isEternal || scope.currentSubray.recursion > 0 ) ) {
+				return;
 
-				// Ray propagation has arrived to this segment
+			} else if ( this.rayParameters.isEternal && scope.currentSubray.recursion == 0 ) {
 
+				// Eternal rays don't propagate nor vanish, but its subrays do
 				scope.createPrism( segment );
-
 				scope.onDecideSubrayCreation( segment, scope );
 
-			}
-
-		} else if ( time < subray.beginVanishingTime ) {
+			} else if ( time < subray.endPropagationTime ) {
 
-			// Ray is steady (nor propagating nor vanishing)
+				if ( scope.timeFraction >= segment.fraction0 * subray.propagationTimeFactor ) {
 
-			scope.createPrism( segment );
+					// Ray propagation has arrived to this segment
+					scope.createPrism( segment );
+					scope.onDecideSubrayCreation( segment, scope );
 
-			scope.onDecideSubrayCreation( segment, scope );
+				}
 
-		} else {
-
-			if ( scope.timeFraction <= subray.vanishingTimeFactor + segment.fraction1 * ( 1 - subray.vanishingTimeFactor ) ) {
-
-				// Segment has not yet vanished
+			} else if ( time < subray.beginVanishingTime ) {
 
+				// Ray is steady (nor propagating nor vanishing)
 				scope.createPrism( segment );
+				scope.onDecideSubrayCreation( segment, scope );
 
-			}
-
-			scope.onDecideSubrayCreation( segment, scope );
-
-		}
-
-	} );
-
-};
-
-THREE.LightningStrike.prototype.addNewSubray = function ( /*rayParameters*/ ) {
-
-	return this.subrays[ this.numSubrays ++ ];
-
-};
-
-THREE.LightningStrike.prototype.initSubray = function ( subray, rayParameters ) {
-
-	subray.pos0.copy( rayParameters.sourceOffset );
-	subray.pos1.copy( rayParameters.destOffset );
-	subray.up0.copy( rayParameters.up0 );
-	subray.up1.copy( rayParameters.up1 );
-	subray.radius0 = rayParameters.radius0;
-	subray.radius1 = rayParameters.radius1;
-	subray.birthTime = rayParameters.birthTime;
-	subray.deathTime = rayParameters.deathTime;
-	subray.timeScale = rayParameters.timeScale;
-	subray.roughness = rayParameters.roughness;
-	subray.straightness = rayParameters.straightness;
-	subray.propagationTimeFactor = rayParameters.propagationTimeFactor;
-	subray.vanishingTimeFactor = rayParameters.vanishingTimeFactor;
-
-	subray.maxIterations = this.maxIterations;
-	subray.seed = rayParameters.noiseSeed !== undefined ? rayParameters.noiseSeed : 0;
-	subray.recursion = 0;
-
-};
-
-THREE.LightningStrike.prototype.fractalRay = function ( time, segmentCallback ) {
-
-	this.time = time;
-	this.currentSegmentCallback = segmentCallback;
-	this.numSubrays = 0;
-
-	// Add the top level subray
-	this.initSubray( this.addNewSubray(), this.rayParameters );
-
-	// Process all subrays that are being generated until consuming all of them
-	for ( var subrayIndex = 0; subrayIndex < this.numSubrays; subrayIndex ++ ) {
-
-		var subray = this.subrays[ subrayIndex ];
-		this.currentSubray = subray;
-
-		this.randomGenerator.setSeed( subray.seed );
-
-		subray.endPropagationTime = THREE.MathUtils.lerp( subray.birthTime, subray.deathTime, subray.propagationTimeFactor );
-		subray.beginVanishingTime = THREE.MathUtils.lerp( subray.deathTime, subray.birthTime, 1 - subray.vanishingTimeFactor );
-
-		var random1 = this.randomGenerator.random;
-		subray.linPos0.set( random1(), random1(), random1() ).multiplyScalar( 1000 );
-		subray.linPos1.set( random1(), random1(), random1() ).multiplyScalar( 1000 );
-
-		this.timeFraction = ( time - subray.birthTime ) / ( subray.deathTime - subray.birthTime );
-
-		this.currentSegmentIndex = 0;
-		this.isInitialSegment = true;
-
-		var segment = this.getNewSegment();
-		segment.iteration = 0;
-		segment.pos0.copy( subray.pos0 );
-		segment.pos1.copy( subray.pos1 );
-		segment.linPos0.copy( subray.linPos0 );
-		segment.linPos1.copy( subray.linPos1 );
-		segment.up0.copy( subray.up0 );
-		segment.up1.copy( subray.up1 );
-		segment.radius0 = subray.radius0;
-		segment.radius1 = subray.radius1;
-		segment.fraction0 = 0;
-		segment.fraction1 = 1;
-		segment.positionVariationFactor = 1 - subray.straightness;
-
-		this.subrayProbability = this.ramification * Math.pow( this.recursionProbability, subray.recursion ) / ( 1 << subray.maxIterations );
-
-		this.fractalRayRecursive( segment );
-
-	}
-
-	this.currentSegmentCallback = null;
-	this.currentSubray = null;
-
-};
-
-THREE.LightningStrike.prototype.fractalRayRecursive = function ( segment ) {
-
-	// Leave recursion condition
-	if ( segment.iteration >= this.currentSubray.maxIterations ) {
-
-		this.currentSegmentCallback( segment );
-
-		return;
-
-	}
-
-	// Interpolation
-	this.forwards.subVectors( segment.pos1, segment.pos0 );
-	var lForwards = this.forwards.length();
-
-	if ( lForwards < 0.000001 ) {
-
-		this.forwards.set( 0, 0, 0.01 );
-		lForwards = this.forwards.length();
-
-	}
-
-	var middleRadius = ( segment.radius0 + segment.radius1 ) * 0.5;
-	var middleFraction = ( segment.fraction0 + segment.fraction1 ) * 0.5;
-
-	var timeDimension = this.time * this.currentSubray.timeScale * Math.pow( 2, segment.iteration );
-
-	this.middlePos.lerpVectors( segment.pos0, segment.pos1, 0.5 );
-	this.middleLinPos.lerpVectors( segment.linPos0, segment.linPos1, 0.5 );
-	var p = this.middleLinPos;
-
-	// Noise
-	this.newPos.set( this.simplexX.noise4d( p.x, p.y, p.z, timeDimension ),
-		this.simplexY.noise4d( p.x, p.y, p.z, timeDimension ),
-		this.simplexZ.noise4d( p.x, p.y, p.z, timeDimension ) );
-
-	this.newPos.multiplyScalar( segment.positionVariationFactor * lForwards );
-	this.newPos.add( this.middlePos );
-
-	// Recursion
-
-	var newSegment1 = this.getNewSegment();
-	newSegment1.pos0.copy( segment.pos0 );
-	newSegment1.pos1.copy( this.newPos );
-	newSegment1.linPos0.copy( segment.linPos0 );
-	newSegment1.linPos1.copy( this.middleLinPos );
-	newSegment1.up0.copy( segment.up0 );
-	newSegment1.up1.copy( segment.up1 );
-	newSegment1.radius0 = segment.radius0;
-	newSegment1.radius1 = middleRadius;
-	newSegment1.fraction0 = segment.fraction0;
-	newSegment1.fraction1 = middleFraction;
-	newSegment1.positionVariationFactor = segment.positionVariationFactor * this.currentSubray.roughness;
-	newSegment1.iteration = segment.iteration + 1;
-
-	var newSegment2 = this.getNewSegment();
-	newSegment2.pos0.copy( this.newPos );
-	newSegment2.pos1.copy( segment.pos1 );
-	newSegment2.linPos0.copy( this.middleLinPos );
-	newSegment2.linPos1.copy( segment.linPos1 );
-	this.cross1.crossVectors( segment.up0, this.forwards.normalize() );
-	newSegment2.up0.crossVectors( this.forwards, this.cross1 ).normalize();
-	newSegment2.up1.copy( segment.up1 );
-	newSegment2.radius0 = middleRadius;
-	newSegment2.radius1 = segment.radius1;
-	newSegment2.fraction0 = middleFraction;
-	newSegment2.fraction1 = segment.fraction1;
-	newSegment2.positionVariationFactor = segment.positionVariationFactor * this.currentSubray.roughness;
-	newSegment2.iteration = segment.iteration + 1;
-
-	this.fractalRayRecursive( newSegment1 );
-
-	this.fractalRayRecursive( newSegment2 );
-
-};
-
-THREE.LightningStrike.prototype.createPrism = function ( segment ) {
-
-	// Creates one triangular prism and its vertices at the segment
-
-	this.forwardsFill.subVectors( segment.pos1, segment.pos0 ).normalize();
-
-	if ( this.isInitialSegment ) {
-
-		this.currentCreateTriangleVertices( segment.pos0, segment.up0, this.forwardsFill, segment.radius0, 0 );
-
-		this.isInitialSegment = false;
-
-	}
+			} else {
 
-	this.currentCreateTriangleVertices( segment.pos1, segment.up0, this.forwardsFill, segment.radius1, segment.fraction1 );
+				if ( scope.timeFraction <= subray.vanishingTimeFactor + segment.fraction1 * ( 1 - subray.vanishingTimeFactor ) ) {
 
-	this.createPrismFaces();
+					// Segment has not yet vanished
+					scope.createPrism( segment );
 
-};
+				}
 
-THREE.LightningStrike.prototype.createTriangleVerticesWithoutUVs = function ( pos, up, forwards, radius ) {
+				scope.onDecideSubrayCreation( segment, scope );
 
-	// Create an equilateral triangle (only vertices)
+			}
 
-	this.side.crossVectors( up, forwards ).multiplyScalar( radius * THREE.LightningStrike.COS30DEG );
-	this.down.copy( up ).multiplyScalar( - radius * THREE.LightningStrike.SIN30DEG );
+		} );
 
-	var p = this.vPos;
-	var v = this.vertices;
+	};
 
-	p.copy( pos ).sub( this.side ).add( this.down );
+	LightningStrike.prototype.addNewSubray = function ( ) {
 
-	v[ this.currentCoordinate ++ ] = p.x;
-	v[ this.currentCoordinate ++ ] = p.y;
-	v[ this.currentCoordinate ++ ] = p.z;
+		return this.subrays[ this.numSubrays ++ ];
 
-	p.copy( pos ).add( this.side ).add( this.down );
+	};
 
-	v[ this.currentCoordinate ++ ] = p.x;
-	v[ this.currentCoordinate ++ ] = p.y;
-	v[ this.currentCoordinate ++ ] = p.z;
+	LightningStrike.prototype.initSubray = function ( subray, rayParameters ) {
+
+		subray.pos0.copy( rayParameters.sourceOffset );
+		subray.pos1.copy( rayParameters.destOffset );
+		subray.up0.copy( rayParameters.up0 );
+		subray.up1.copy( rayParameters.up1 );
+		subray.radius0 = rayParameters.radius0;
+		subray.radius1 = rayParameters.radius1;
+		subray.birthTime = rayParameters.birthTime;
+		subray.deathTime = rayParameters.deathTime;
+		subray.timeScale = rayParameters.timeScale;
+		subray.roughness = rayParameters.roughness;
+		subray.straightness = rayParameters.straightness;
+		subray.propagationTimeFactor = rayParameters.propagationTimeFactor;
+		subray.vanishingTimeFactor = rayParameters.vanishingTimeFactor;
+		subray.maxIterations = this.maxIterations;
+		subray.seed = rayParameters.noiseSeed !== undefined ? rayParameters.noiseSeed : 0;
+		subray.recursion = 0;
 
-	p.copy( up ).multiplyScalar( radius ).add( pos );
+	};
 
-	v[ this.currentCoordinate ++ ] = p.x;
-	v[ this.currentCoordinate ++ ] = p.y;
-	v[ this.currentCoordinate ++ ] = p.z;
+	LightningStrike.prototype.fractalRay = function ( time, segmentCallback ) {
+
+		this.time = time;
+		this.currentSegmentCallback = segmentCallback;
+		this.numSubrays = 0; // Add the top level subray
+
+		this.initSubray( this.addNewSubray(), this.rayParameters ); // Process all subrays that are being generated until consuming all of them
+
+		for ( var subrayIndex = 0; subrayIndex < this.numSubrays; subrayIndex ++ ) {
+
+			var subray = this.subrays[ subrayIndex ];
+			this.currentSubray = subray;
+			this.randomGenerator.setSeed( subray.seed );
+			subray.endPropagationTime = THREE.MathUtils.lerp( subray.birthTime, subray.deathTime, subray.propagationTimeFactor );
+			subray.beginVanishingTime = THREE.MathUtils.lerp( subray.deathTime, subray.birthTime, 1 - subray.vanishingTimeFactor );
+			var random1 = this.randomGenerator.random;
+			subray.linPos0.set( random1(), random1(), random1() ).multiplyScalar( 1000 );
+			subray.linPos1.set( random1(), random1(), random1() ).multiplyScalar( 1000 );
+			this.timeFraction = ( time - subray.birthTime ) / ( subray.deathTime - subray.birthTime );
+			this.currentSegmentIndex = 0;
+			this.isInitialSegment = true;
+			var segment = this.getNewSegment();
+			segment.iteration = 0;
+			segment.pos0.copy( subray.pos0 );
+			segment.pos1.copy( subray.pos1 );
+			segment.linPos0.copy( subray.linPos0 );
+			segment.linPos1.copy( subray.linPos1 );
+			segment.up0.copy( subray.up0 );
+			segment.up1.copy( subray.up1 );
+			segment.radius0 = subray.radius0;
+			segment.radius1 = subray.radius1;
+			segment.fraction0 = 0;
+			segment.fraction1 = 1;
+			segment.positionVariationFactor = 1 - subray.straightness;
+			this.subrayProbability = this.ramification * Math.pow( this.recursionProbability, subray.recursion ) / ( 1 << subray.maxIterations );
+			this.fractalRayRecursive( segment );
 
-	this.currentVertex += 3;
+		}
 
-};
+		this.currentSegmentCallback = null;
+		this.currentSubray = null;
 
-THREE.LightningStrike.prototype.createTriangleVerticesWithUVs = function ( pos, up, forwards, radius, u ) {
+	};
 
-	// Create an equilateral triangle (only vertices)
+	LightningStrike.prototype.fractalRayRecursive = function ( segment ) {
 
-	this.side.crossVectors( up, forwards ).multiplyScalar( radius * THREE.LightningStrike.COS30DEG );
-	this.down.copy( up ).multiplyScalar( - radius * THREE.LightningStrike.SIN30DEG );
+		// Leave recursion condition
+		if ( segment.iteration >= this.currentSubray.maxIterations ) {
 
-	var p = this.vPos;
-	var v = this.vertices;
-	var uv = this.uvs;
+			this.currentSegmentCallback( segment );
+			return;
 
-	p.copy( pos ).sub( this.side ).add( this.down );
+		} // Interpolation
 
-	v[ this.currentCoordinate ++ ] = p.x;
-	v[ this.currentCoordinate ++ ] = p.y;
-	v[ this.currentCoordinate ++ ] = p.z;
 
-	uv[ this.currentUVCoordinate ++ ] = u;
-	uv[ this.currentUVCoordinate ++ ] = 0;
+		this.forwards.subVectors( segment.pos1, segment.pos0 );
+		var lForwards = this.forwards.length();
 
-	p.copy( pos ).add( this.side ).add( this.down );
+		if ( lForwards < 0.000001 ) {
 
-	v[ this.currentCoordinate ++ ] = p.x;
-	v[ this.currentCoordinate ++ ] = p.y;
-	v[ this.currentCoordinate ++ ] = p.z;
+			this.forwards.set( 0, 0, 0.01 );
+			lForwards = this.forwards.length();
 
-	uv[ this.currentUVCoordinate ++ ] = u;
-	uv[ this.currentUVCoordinate ++ ] = 0.5;
+		}
 
-	p.copy( up ).multiplyScalar( radius ).add( pos );
+		var middleRadius = ( segment.radius0 + segment.radius1 ) * 0.5;
+		var middleFraction = ( segment.fraction0 + segment.fraction1 ) * 0.5;
+		var timeDimension = this.time * this.currentSubray.timeScale * Math.pow( 2, segment.iteration );
+		this.middlePos.lerpVectors( segment.pos0, segment.pos1, 0.5 );
+		this.middleLinPos.lerpVectors( segment.linPos0, segment.linPos1, 0.5 );
+		var p = this.middleLinPos; // Noise
+
+		this.newPos.set( this.simplexX.noise4d( p.x, p.y, p.z, timeDimension ), this.simplexY.noise4d( p.x, p.y, p.z, timeDimension ), this.simplexZ.noise4d( p.x, p.y, p.z, timeDimension ) );
+		this.newPos.multiplyScalar( segment.positionVariationFactor * lForwards );
+		this.newPos.add( this.middlePos ); // Recursion
+
+		var newSegment1 = this.getNewSegment();
+		newSegment1.pos0.copy( segment.pos0 );
+		newSegment1.pos1.copy( this.newPos );
+		newSegment1.linPos0.copy( segment.linPos0 );
+		newSegment1.linPos1.copy( this.middleLinPos );
+		newSegment1.up0.copy( segment.up0 );
+		newSegment1.up1.copy( segment.up1 );
+		newSegment1.radius0 = segment.radius0;
+		newSegment1.radius1 = middleRadius;
+		newSegment1.fraction0 = segment.fraction0;
+		newSegment1.fraction1 = middleFraction;
+		newSegment1.positionVariationFactor = segment.positionVariationFactor * this.currentSubray.roughness;
+		newSegment1.iteration = segment.iteration + 1;
+		var newSegment2 = this.getNewSegment();
+		newSegment2.pos0.copy( this.newPos );
+		newSegment2.pos1.copy( segment.pos1 );
+		newSegment2.linPos0.copy( this.middleLinPos );
+		newSegment2.linPos1.copy( segment.linPos1 );
+		this.cross1.crossVectors( segment.up0, this.forwards.normalize() );
+		newSegment2.up0.crossVectors( this.forwards, this.cross1 ).normalize();
+		newSegment2.up1.copy( segment.up1 );
+		newSegment2.radius0 = middleRadius;
+		newSegment2.radius1 = segment.radius1;
+		newSegment2.fraction0 = middleFraction;
+		newSegment2.fraction1 = segment.fraction1;
+		newSegment2.positionVariationFactor = segment.positionVariationFactor * this.currentSubray.roughness;
+		newSegment2.iteration = segment.iteration + 1;
+		this.fractalRayRecursive( newSegment1 );
+		this.fractalRayRecursive( newSegment2 );
 
-	v[ this.currentCoordinate ++ ] = p.x;
-	v[ this.currentCoordinate ++ ] = p.y;
-	v[ this.currentCoordinate ++ ] = p.z;
+	};
 
-	uv[ this.currentUVCoordinate ++ ] = u;
-	uv[ this.currentUVCoordinate ++ ] = 1;
+	LightningStrike.prototype.createPrism = function ( segment ) {
 
-	this.currentVertex += 3;
+		// Creates one triangular prism and its vertices at the segment
+		this.forwardsFill.subVectors( segment.pos1, segment.pos0 ).normalize();
 
-};
+		if ( this.isInitialSegment ) {
 
-THREE.LightningStrike.prototype.createPrismFaces = function ( vertex/*, index*/ ) {
+			this.currentCreateTriangleVertices( segment.pos0, segment.up0, this.forwardsFill, segment.radius0, 0 );
+			this.isInitialSegment = false;
 
-	var indices = this.indices;
-	var vertex = this.currentVertex - 6;
+		}
 
-	indices[ this.currentIndex ++ ] = vertex + 1;
-	indices[ this.currentIndex ++ ] = vertex + 2;
-	indices[ this.currentIndex ++ ] = vertex + 5;
-	indices[ this.currentIndex ++ ] = vertex + 1;
-	indices[ this.currentIndex ++ ] = vertex + 5;
-	indices[ this.currentIndex ++ ] = vertex + 4;
-	indices[ this.currentIndex ++ ] = vertex + 0;
-	indices[ this.currentIndex ++ ] = vertex + 1;
-	indices[ this.currentIndex ++ ] = vertex + 4;
-	indices[ this.currentIndex ++ ] = vertex + 0;
-	indices[ this.currentIndex ++ ] = vertex + 4;
-	indices[ this.currentIndex ++ ] = vertex + 3;
-	indices[ this.currentIndex ++ ] = vertex + 2;
-	indices[ this.currentIndex ++ ] = vertex + 0;
-	indices[ this.currentIndex ++ ] = vertex + 3;
-	indices[ this.currentIndex ++ ] = vertex + 2;
-	indices[ this.currentIndex ++ ] = vertex + 3;
-	indices[ this.currentIndex ++ ] = vertex + 5;
+		this.currentCreateTriangleVertices( segment.pos1, segment.up0, this.forwardsFill, segment.radius1, segment.fraction1 );
+		this.createPrismFaces();
 
-};
+	};
 
-THREE.LightningStrike.prototype.createDefaultSubrayCreationCallbacks = function () {
+	LightningStrike.prototype.createTriangleVerticesWithoutUVs = function ( pos, up, forwards, radius ) {
+
+		// Create an equilateral triangle (only vertices)
+		this.side.crossVectors( up, forwards ).multiplyScalar( radius * LightningStrike.COS30DEG );
+		this.down.copy( up ).multiplyScalar( - radius * LightningStrike.SIN30DEG );
+		var p = this.vPos;
+		var v = this.vertices;
+		p.copy( pos ).sub( this.side ).add( this.down );
+		v[ this.currentCoordinate ++ ] = p.x;
+		v[ this.currentCoordinate ++ ] = p.y;
+		v[ this.currentCoordinate ++ ] = p.z;
+		p.copy( pos ).add( this.side ).add( this.down );
+		v[ this.currentCoordinate ++ ] = p.x;
+		v[ this.currentCoordinate ++ ] = p.y;
+		v[ this.currentCoordinate ++ ] = p.z;
+		p.copy( up ).multiplyScalar( radius ).add( pos );
+		v[ this.currentCoordinate ++ ] = p.x;
+		v[ this.currentCoordinate ++ ] = p.y;
+		v[ this.currentCoordinate ++ ] = p.z;
+		this.currentVertex += 3;
 
-	var random1 = this.randomGenerator.random;
+	};
 
-	this.onDecideSubrayCreation = function ( segment, lightningStrike ) {
+	LightningStrike.prototype.createTriangleVerticesWithUVs = function ( pos, up, forwards, radius, u ) {
+
+		// Create an equilateral triangle (only vertices)
+		this.side.crossVectors( up, forwards ).multiplyScalar( radius * LightningStrike.COS30DEG );
+		this.down.copy( up ).multiplyScalar( - radius * LightningStrike.SIN30DEG );
+		var p = this.vPos;
+		var v = this.vertices;
+		var uv = this.uvs;
+		p.copy( pos ).sub( this.side ).add( this.down );
+		v[ this.currentCoordinate ++ ] = p.x;
+		v[ this.currentCoordinate ++ ] = p.y;
+		v[ this.currentCoordinate ++ ] = p.z;
+		uv[ this.currentUVCoordinate ++ ] = u;
+		uv[ this.currentUVCoordinate ++ ] = 0;
+		p.copy( pos ).add( this.side ).add( this.down );
+		v[ this.currentCoordinate ++ ] = p.x;
+		v[ this.currentCoordinate ++ ] = p.y;
+		v[ this.currentCoordinate ++ ] = p.z;
+		uv[ this.currentUVCoordinate ++ ] = u;
+		uv[ this.currentUVCoordinate ++ ] = 0.5;
+		p.copy( up ).multiplyScalar( radius ).add( pos );
+		v[ this.currentCoordinate ++ ] = p.x;
+		v[ this.currentCoordinate ++ ] = p.y;
+		v[ this.currentCoordinate ++ ] = p.z;
+		uv[ this.currentUVCoordinate ++ ] = u;
+		uv[ this.currentUVCoordinate ++ ] = 1;
+		this.currentVertex += 3;
 
-		// Decide subrays creation at parent (sub)ray segment
+	};
 
-		var subray = lightningStrike.currentSubray;
+	LightningStrike.prototype.createPrismFaces = function ( vertex
+		/*, index*/
+	) {
+
+		var indices = this.indices;
+		var vertex = this.currentVertex - 6;
+		indices[ this.currentIndex ++ ] = vertex + 1;
+		indices[ this.currentIndex ++ ] = vertex + 2;
+		indices[ this.currentIndex ++ ] = vertex + 5;
+		indices[ this.currentIndex ++ ] = vertex + 1;
+		indices[ this.currentIndex ++ ] = vertex + 5;
+		indices[ this.currentIndex ++ ] = vertex + 4;
+		indices[ this.currentIndex ++ ] = vertex + 0;
+		indices[ this.currentIndex ++ ] = vertex + 1;
+		indices[ this.currentIndex ++ ] = vertex + 4;
+		indices[ this.currentIndex ++ ] = vertex + 0;
+		indices[ this.currentIndex ++ ] = vertex + 4;
+		indices[ this.currentIndex ++ ] = vertex + 3;
+		indices[ this.currentIndex ++ ] = vertex + 2;
+		indices[ this.currentIndex ++ ] = vertex + 0;
+		indices[ this.currentIndex ++ ] = vertex + 3;
+		indices[ this.currentIndex ++ ] = vertex + 2;
+		indices[ this.currentIndex ++ ] = vertex + 3;
+		indices[ this.currentIndex ++ ] = vertex + 5;
 
-		var period = lightningStrike.rayParameters.subrayPeriod;
-		var dutyCycle = lightningStrike.rayParameters.subrayDutyCycle;
+	};
 
-		var phase0 = ( lightningStrike.rayParameters.isEternal && subray.recursion == 0 ) ? - random1() * period : THREE.MathUtils.lerp( subray.birthTime, subray.endPropagationTime, segment.fraction0 ) - random1() * period;
+	LightningStrike.prototype.createDefaultSubrayCreationCallbacks = function () {
 
-		var phase = lightningStrike.time - phase0;
-		var currentCycle = Math.floor( phase / period );
+		var random1 = this.randomGenerator.random;
 
-		var childSubraySeed = random1() * ( currentCycle + 1 );
+		this.onDecideSubrayCreation = function ( segment, lightningStrike ) {
 
-		var isActive = phase % period <= dutyCycle * period;
+			// Decide subrays creation at parent (sub)ray segment
+			var subray = lightningStrike.currentSubray;
+			var period = lightningStrike.rayParameters.subrayPeriod;
+			var dutyCycle = lightningStrike.rayParameters.subrayDutyCycle;
+			var phase0 = lightningStrike.rayParameters.isEternal && subray.recursion == 0 ? - random1() * period : THREE.MathUtils.lerp( subray.birthTime, subray.endPropagationTime, segment.fraction0 ) - random1() * period;
+			var phase = lightningStrike.time - phase0;
+			var currentCycle = Math.floor( phase / period );
+			var childSubraySeed = random1() * ( currentCycle + 1 );
+			var isActive = phase % period <= dutyCycle * period;
+			var probability = 0;
 
-		var probability = 0;
+			if ( isActive ) {
 
-		if ( isActive ) {
+				probability = lightningStrike.subrayProbability; // Distribution test: probability *= segment.fraction0 > 0.5 && segment.fraction0 < 0.9 ? 1 / 0.4 : 0;
 
-			probability = lightningStrike.subrayProbability;
-			// Distribution test: probability *= segment.fraction0 > 0.5 && segment.fraction0 < 0.9 ? 1 / 0.4 : 0;
+			}
 
-		}
+			if ( subray.recursion < lightningStrike.maxSubrayRecursion && lightningStrike.numSubrays < lightningStrike.maxSubrays && random1() < probability ) {
+
+				var childSubray = lightningStrike.addNewSubray();
+				var parentSeed = lightningStrike.randomGenerator.getSeed();
+				childSubray.seed = childSubraySeed;
+				lightningStrike.randomGenerator.setSeed( childSubraySeed );
+				childSubray.recursion = subray.recursion + 1;
+				childSubray.maxIterations = Math.max( 1, subray.maxIterations - 1 );
+				childSubray.linPos0.set( random1(), random1(), random1() ).multiplyScalar( 1000 );
+				childSubray.linPos1.set( random1(), random1(), random1() ).multiplyScalar( 1000 );
+				childSubray.up0.copy( subray.up0 );
+				childSubray.up1.copy( subray.up1 );
+				childSubray.radius0 = segment.radius0 * lightningStrike.rayParameters.radius0Factor;
+				childSubray.radius1 = Math.min( lightningStrike.rayParameters.minRadius, segment.radius1 * lightningStrike.rayParameters.radius1Factor );
+				childSubray.birthTime = phase0 + currentCycle * period;
+				childSubray.deathTime = childSubray.birthTime + period * dutyCycle;
+
+				if ( ! lightningStrike.rayParameters.isEternal && subray.recursion == 0 ) {
+
+					childSubray.birthTime = Math.max( childSubray.birthTime, subray.birthTime );
+					childSubray.deathTime = Math.min( childSubray.deathTime, subray.deathTime );
+
+				}
+
+				childSubray.timeScale = subray.timeScale * 2;
+				childSubray.roughness = subray.roughness;
+				childSubray.straightness = subray.straightness;
+				childSubray.propagationTimeFactor = subray.propagationTimeFactor;
+				childSubray.vanishingTimeFactor = subray.vanishingTimeFactor;
+				lightningStrike.onSubrayCreation( segment, subray, childSubray, lightningStrike );
+				lightningStrike.randomGenerator.setSeed( parentSeed );
 
-		if ( subray.recursion < lightningStrike.maxSubrayRecursion && lightningStrike.numSubrays < lightningStrike.maxSubrays && random1() < probability ) {
+			}
 
-			var childSubray = lightningStrike.addNewSubray();
+		};
 
-			var parentSeed = lightningStrike.randomGenerator.getSeed();
-			childSubray.seed = childSubraySeed;
-			lightningStrike.randomGenerator.setSeed( childSubraySeed );
+		var vec1Pos = new THREE.Vector3();
+		var vec2Forward = new THREE.Vector3();
+		var vec3Side = new THREE.Vector3();
+		var vec4Up = new THREE.Vector3();
 
-			childSubray.recursion = subray.recursion + 1;
-			childSubray.maxIterations = Math.max( 1, subray.maxIterations - 1 );
+		this.onSubrayCreation = function ( segment, parentSubray, childSubray, lightningStrike ) {
 
-			childSubray.linPos0.set( random1(), random1(), random1() ).multiplyScalar( 1000 );
-			childSubray.linPos1.set( random1(), random1(), random1() ).multiplyScalar( 1000 );
-			childSubray.up0.copy( subray.up0 );
-			childSubray.up1.copy( subray.up1 );
-			childSubray.radius0 = segment.radius0 * lightningStrike.rayParameters.radius0Factor;
-			childSubray.radius1 = Math.min( lightningStrike.rayParameters.minRadius, segment.radius1 * lightningStrike.rayParameters.radius1Factor );
+			// Decide childSubray origin and destination positions (pos0 and pos1) and possibly other properties of childSubray
+			// Just use the default cone position generator
+			lightningStrike.subrayCylinderPosition( segment, parentSubray, childSubray, 0.5, 0.6, 0.2 );
 
-			childSubray.birthTime = phase0 + ( currentCycle ) * period;
-			childSubray.deathTime = childSubray.birthTime + period * dutyCycle;
+		};
 
-			if ( ! lightningStrike.rayParameters.isEternal && subray.recursion == 0 ) {
+		this.subrayConePosition = function ( segment, parentSubray, childSubray, heightFactor, sideWidthFactor, minSideWidthFactor ) {
 
-				childSubray.birthTime = Math.max( childSubray.birthTime, subray.birthTime );
-				childSubray.deathTime = Math.min( childSubray.deathTime, subray.deathTime );
+			// Sets childSubray pos0 and pos1 in a cone
+			childSubray.pos0.copy( segment.pos0 );
+			vec1Pos.subVectors( parentSubray.pos1, parentSubray.pos0 );
+			vec2Forward.copy( vec1Pos ).normalize();
+			vec1Pos.multiplyScalar( segment.fraction0 + ( 1 - segment.fraction0 ) * ( random1() * heightFactor ) );
+			var length = vec1Pos.length();
+			vec3Side.crossVectors( parentSubray.up0, vec2Forward );
+			var angle = 2 * Math.PI * random1();
+			vec3Side.multiplyScalar( Math.cos( angle ) );
+			vec4Up.copy( parentSubray.up0 ).multiplyScalar( Math.sin( angle ) );
+			childSubray.pos1.copy( vec3Side ).add( vec4Up ).multiplyScalar( length * sideWidthFactor * ( minSideWidthFactor + random1() * ( 1 - minSideWidthFactor ) ) ).add( vec1Pos ).add( parentSubray.pos0 );
 
-			}
+		};
 
-			childSubray.timeScale = subray.timeScale * 2;
-			childSubray.roughness = subray.roughness;
-			childSubray.straightness = subray.straightness;
-			childSubray.propagationTimeFactor = subray.propagationTimeFactor;
-			childSubray.vanishingTimeFactor = subray.vanishingTimeFactor;
+		this.subrayCylinderPosition = function ( segment, parentSubray, childSubray, heightFactor, sideWidthFactor, minSideWidthFactor ) {
 
-			lightningStrike.onSubrayCreation( segment, subray, childSubray, lightningStrike );
+			// Sets childSubray pos0 and pos1 in a cylinder
+			childSubray.pos0.copy( segment.pos0 );
+			vec1Pos.subVectors( parentSubray.pos1, parentSubray.pos0 );
+			vec2Forward.copy( vec1Pos ).normalize();
+			vec1Pos.multiplyScalar( segment.fraction0 + ( 1 - segment.fraction0 ) * ( ( 2 * random1() - 1 ) * heightFactor ) );
+			var length = vec1Pos.length();
+			vec3Side.crossVectors( parentSubray.up0, vec2Forward );
+			var angle = 2 * Math.PI * random1();
+			vec3Side.multiplyScalar( Math.cos( angle ) );
+			vec4Up.copy( parentSubray.up0 ).multiplyScalar( Math.sin( angle ) );
+			childSubray.pos1.copy( vec3Side ).add( vec4Up ).multiplyScalar( length * sideWidthFactor * ( minSideWidthFactor + random1() * ( 1 - minSideWidthFactor ) ) ).add( vec1Pos ).add( parentSubray.pos0 );
 
-			lightningStrike.randomGenerator.setSeed( parentSeed );
-
-		}
+		};
 
 	};
 
-	var vec1Pos = new THREE.Vector3();
-	var vec2Forward = new THREE.Vector3();
-	var vec3Side = new THREE.Vector3();
-	var vec4Up = new THREE.Vector3();
-
-	this.onSubrayCreation = function ( segment, parentSubray, childSubray, lightningStrike ) {
-
-		// Decide childSubray origin and destination positions (pos0 and pos1) and possibly other properties of childSubray
-
-		// Just use the default cone position generator
-		lightningStrike.subrayCylinderPosition( segment, parentSubray, childSubray, 0.5, 0.6, 0.2 );
+	LightningStrike.prototype.createSubray = function () {
+
+		return {
+			seed: 0,
+			maxIterations: 0,
+			recursion: 0,
+			pos0: new THREE.Vector3(),
+			pos1: new THREE.Vector3(),
+			linPos0: new THREE.Vector3(),
+			linPos1: new THREE.Vector3(),
+			up0: new THREE.Vector3(),
+			up1: new THREE.Vector3(),
+			radius0: 0,
+			radius1: 0,
+			birthTime: 0,
+			deathTime: 0,
+			timeScale: 0,
+			roughness: 0,
+			straightness: 0,
+			propagationTimeFactor: 0,
+			vanishingTimeFactor: 0,
+			endPropagationTime: 0,
+			beginVanishingTime: 0
+		};
 
 	};
 
-	this.subrayConePosition = function ( segment, parentSubray, childSubray, heightFactor, sideWidthFactor, minSideWidthFactor ) {
-
-		// Sets childSubray pos0 and pos1 in a cone
-
-		childSubray.pos0.copy( segment.pos0 );
-
-		vec1Pos.subVectors( parentSubray.pos1, parentSubray.pos0 );
-		vec2Forward.copy( vec1Pos ).normalize();
-		vec1Pos.multiplyScalar( segment.fraction0 + ( 1 - segment.fraction0 ) * ( random1() * heightFactor ) );
-		var length = vec1Pos.length();
-		vec3Side.crossVectors( parentSubray.up0, vec2Forward );
-		var angle = 2 * Math.PI * random1();
-		vec3Side.multiplyScalar( Math.cos( angle ) );
-		vec4Up.copy( parentSubray.up0 ).multiplyScalar( Math.sin( angle ) );
-
-		childSubray.pos1.copy( vec3Side ).add( vec4Up ).multiplyScalar( length * sideWidthFactor * ( minSideWidthFactor + random1() * ( 1 - minSideWidthFactor ) ) ).add( vec1Pos ).add( parentSubray.pos0 );
+	LightningStrike.prototype.createSegment = function () {
+
+		return {
+			iteration: 0,
+			pos0: new THREE.Vector3(),
+			pos1: new THREE.Vector3(),
+			linPos0: new THREE.Vector3(),
+			linPos1: new THREE.Vector3(),
+			up0: new THREE.Vector3(),
+			up1: new THREE.Vector3(),
+			radius0: 0,
+			radius1: 0,
+			fraction0: 0,
+			fraction1: 0,
+			positionVariationFactor: 0
+		};
 
 	};
 
-	this.subrayCylinderPosition = function ( segment, parentSubray, childSubray, heightFactor, sideWidthFactor, minSideWidthFactor ) {
-
-		// Sets childSubray pos0 and pos1 in a cylinder
-
-		childSubray.pos0.copy( segment.pos0 );
+	LightningStrike.prototype.getNewSegment = function () {
 
-		vec1Pos.subVectors( parentSubray.pos1, parentSubray.pos0 );
-		vec2Forward.copy( vec1Pos ).normalize();
-		vec1Pos.multiplyScalar( segment.fraction0 + ( 1 - segment.fraction0 ) * ( ( 2 * random1() - 1 ) * heightFactor ) );
-		var length = vec1Pos.length();
-		vec3Side.crossVectors( parentSubray.up0, vec2Forward );
-		var angle = 2 * Math.PI * random1();
-		vec3Side.multiplyScalar( Math.cos( angle ) );
-		vec4Up.copy( parentSubray.up0 ).multiplyScalar( Math.sin( angle ) );
-
-		childSubray.pos1.copy( vec3Side ).add( vec4Up ).multiplyScalar( length * sideWidthFactor * ( minSideWidthFactor + random1() * ( 1 - minSideWidthFactor ) ) ).add( vec1Pos ).add( parentSubray.pos0 );
+		return this.raySegments[ this.currentSegmentIndex ++ ];
 
 	};
 
-};
-
-THREE.LightningStrike.prototype.createSubray = function () {
-
-	return {
-
-		seed: 0,
-		maxIterations: 0,
-		recursion: 0,
-		pos0: new THREE.Vector3(),
-		pos1: new THREE.Vector3(),
-		linPos0: new THREE.Vector3(),
-		linPos1: new THREE.Vector3(),
-		up0: new THREE.Vector3(),
-		up1: new THREE.Vector3(),
-		radius0: 0,
-		radius1: 0,
-		birthTime: 0,
-		deathTime: 0,
-		timeScale: 0,
-		roughness: 0,
-		straightness: 0,
-		propagationTimeFactor: 0,
-		vanishingTimeFactor: 0,
-		endPropagationTime: 0,
-		beginVanishingTime: 0
+	LightningStrike.prototype.copy = function ( source ) {
 
-	};
+		THREE.BufferGeometry.prototype.copy.call( this, source );
+		this.init( LightningStrike.copyParameters( {}, source.rayParameters ) );
+		return this;
 
-};
-
-THREE.LightningStrike.prototype.createSegment = function () {
-
-	return {
-		iteration: 0,
-		pos0: new THREE.Vector3(),
-		pos1: new THREE.Vector3(),
-		linPos0: new THREE.Vector3(),
-		linPos1: new THREE.Vector3(),
-		up0: new THREE.Vector3(),
-		up1: new THREE.Vector3(),
-		radius0: 0,
-		radius1: 0,
-		fraction0: 0,
-		fraction1: 0,
-		positionVariationFactor: 0
 	};
 
-};
-
-THREE.LightningStrike.prototype.getNewSegment = function () {
-
-	return this.raySegments[ this.currentSegmentIndex ++ ];
-
-};
+	LightningStrike.prototype.clone = function () {
 
-THREE.LightningStrike.prototype.copy = function ( source ) {
+		return new this.constructor( LightningStrike.copyParameters( {}, this.rayParameters ) );
 
-	THREE.BufferGeometry.prototype.copy.call( this, source );
-
-	this.init( THREE.LightningStrike.copyParameters( {}, source.rayParameters ) );
-
-	return this;
-
-};
-
-THREE.LightningStrike.prototype.clone = function () {
+	};
 
-	return new this.constructor( THREE.LightningStrike.copyParameters( {}, this.rayParameters ) );
+	THREE.LightningStrike = LightningStrike;
 
-};
+} )();

+ 163 - 194
examples/js/geometries/ParametricGeometries.js

@@ -1,252 +1,221 @@
-/**
+( function () {
+
+	/**
  * Experimenting of primitive geometry creation using Surface Parametric equations
  */
 
-THREE.ParametricGeometries = {
-
-	klein: function ( v, u, target ) {
-
-		u *= Math.PI;
-		v *= 2 * Math.PI;
-
-		u = u * 2;
-		var x, y, z;
-		if ( u < Math.PI ) {
-
-			x = 3 * Math.cos( u ) * ( 1 + Math.sin( u ) ) + ( 2 * ( 1 - Math.cos( u ) / 2 ) ) * Math.cos( u ) * Math.cos( v );
-			z = - 8 * Math.sin( u ) - 2 * ( 1 - Math.cos( u ) / 2 ) * Math.sin( u ) * Math.cos( v );
-
-		} else {
-
-			x = 3 * Math.cos( u ) * ( 1 + Math.sin( u ) ) + ( 2 * ( 1 - Math.cos( u ) / 2 ) ) * Math.cos( v + Math.PI );
-			z = - 8 * Math.sin( u );
-
-		}
+	var ParametricGeometries = {
+		klein: function ( v, u, target ) {
 
-		y = - 2 * ( 1 - Math.cos( u ) / 2 ) * Math.sin( v );
+			u *= Math.PI;
+			v *= 2 * Math.PI;
+			u = u * 2;
+			var x, y, z;
 
-		target.set( x, y, z );
+			if ( u < Math.PI ) {
 
-	},
+				x = 3 * Math.cos( u ) * ( 1 + Math.sin( u ) ) + 2 * ( 1 - Math.cos( u ) / 2 ) * Math.cos( u ) * Math.cos( v );
+				z = - 8 * Math.sin( u ) - 2 * ( 1 - Math.cos( u ) / 2 ) * Math.sin( u ) * Math.cos( v );
 
-	plane: function ( width, height ) {
+			} else {
 
-		return function ( u, v, target ) {
+				x = 3 * Math.cos( u ) * ( 1 + Math.sin( u ) ) + 2 * ( 1 - Math.cos( u ) / 2 ) * Math.cos( v + Math.PI );
+				z = - 8 * Math.sin( u );
 
-			var x = u * width;
-			var y = 0;
-			var z = v * height;
+			}
 
+			y = - 2 * ( 1 - Math.cos( u ) / 2 ) * Math.sin( v );
 			target.set( x, y, z );
 
-		};
-
-	},
-
-	mobius: function ( u, t, target ) {
-
-		// flat mobius strip
-		// http://www.wolframalpha.com/input/?i=M%C3%B6bius+strip+parametric+equations&lk=1&a=ClashPrefs_*Surface.MoebiusStrip.SurfaceProperty.ParametricEquations-
-		u = u - 0.5;
-		var v = 2 * Math.PI * t;
-
-		var x, y, z;
+		},
+		plane: function ( width, height ) {
 
-		var a = 2;
+			return function ( u, v, target ) {
 
-		x = Math.cos( v ) * ( a + u * Math.cos( v / 2 ) );
-		y = Math.sin( v ) * ( a + u * Math.cos( v / 2 ) );
-		z = u * Math.sin( v / 2 );
+				var x = u * width;
+				var y = 0;
+				var z = v * height;
+				target.set( x, y, z );
 
-		target.set( x, y, z );
+			};
 
-	},
+		},
+		mobius: function ( u, t, target ) {
 
-	mobius3d: function ( u, t, target ) {
-
-		// volumetric mobius strip
-
-		u *= Math.PI;
-		t *= 2 * Math.PI;
-
-		u = u * 2;
-		var phi = u / 2;
-		var major = 2.25, a = 0.125, b = 0.65;
-
-		var x, y, z;
-
-		x = a * Math.cos( t ) * Math.cos( phi ) - b * Math.sin( t ) * Math.sin( phi );
-		z = a * Math.cos( t ) * Math.sin( phi ) + b * Math.sin( t ) * Math.cos( phi );
-		y = ( major + x ) * Math.sin( u );
-		x = ( major + x ) * Math.cos( u );
-
-		target.set( x, y, z );
-
-	}
-
-};
+			// flat mobius strip
+			// http://www.wolframalpha.com/input/?i=M%C3%B6bius+strip+parametric+equations&lk=1&a=ClashPrefs_*Surface.MoebiusStrip.SurfaceProperty.ParametricEquations-
+			u = u - 0.5;
+			var v = 2 * Math.PI * t;
+			var x, y, z;
+			var a = 2;
+			x = Math.cos( v ) * ( a + u * Math.cos( v / 2 ) );
+			y = Math.sin( v ) * ( a + u * Math.cos( v / 2 ) );
+			z = u * Math.sin( v / 2 );
+			target.set( x, y, z );
 
+		},
+		mobius3d: function ( u, t, target ) {
+
+			// volumetric mobius strip
+			u *= Math.PI;
+			t *= 2 * Math.PI;
+			u = u * 2;
+			var phi = u / 2;
+			var major = 2.25,
+				a = 0.125,
+				b = 0.65;
+			var x, y, z;
+			x = a * Math.cos( t ) * Math.cos( phi ) - b * Math.sin( t ) * Math.sin( phi );
+			z = a * Math.cos( t ) * Math.sin( phi ) + b * Math.sin( t ) * Math.cos( phi );
+			y = ( major + x ) * Math.sin( u );
+			x = ( major + x ) * Math.cos( u );
+			target.set( x, y, z );
 
-/*********************************************
+		}
+	};
+	/*********************************************
  *
  * Parametric Replacement for TubeGeometry
  *
  *********************************************/
 
-THREE.ParametricGeometries.TubeGeometry = function ( path, segments, radius, segmentsRadius, closed ) {
-
-	this.path = path;
-	this.segments = segments || 64;
-	this.radius = radius || 1;
-	this.segmentsRadius = segmentsRadius || 8;
-	this.closed = closed || false;
-
-	var scope = this, numpoints = this.segments + 1;
-
-	var frames = path.computeFrenetFrames( segments, closed ),
-		tangents = frames.tangents,
-		normals = frames.normals,
-		binormals = frames.binormals;
-
-	// proxy internals
+	ParametricGeometries.TubeGeometry = function ( path, segments, radius, segmentsRadius, closed ) {
+
+		this.path = path;
+		this.segments = segments || 64;
+		this.radius = radius || 1;
+		this.segmentsRadius = segmentsRadius || 8;
+		this.closed = closed || false;
+		var scope = this,
+			numpoints = this.segments + 1;
+		var frames = path.computeFrenetFrames( segments, closed ),
+			tangents = frames.tangents,
+			normals = frames.normals,
+			binormals = frames.binormals; // proxy internals
+
+		this.tangents = tangents;
+		this.normals = normals;
+		this.binormals = binormals;
+		var position = new THREE.Vector3();
+
+		var ParametricTube = function ( u, v, target ) {
+
+			v *= 2 * Math.PI;
+			var i = u * ( numpoints - 1 );
+			i = Math.floor( i );
+			path.getPointAt( u, position );
+			var normal = normals[ i ];
+			var binormal = binormals[ i ];
+			var cx = - scope.radius * Math.cos( v ); // TODO: Hack: Negating it so it faces outside.
+
+			var cy = scope.radius * Math.sin( v );
+			position.x += cx * normal.x + cy * binormal.x;
+			position.y += cx * normal.y + cy * binormal.y;
+			position.z += cx * normal.z + cy * binormal.z;
+			target.copy( position );
 
-	this.tangents = tangents;
-	this.normals = normals;
-	this.binormals = binormals;
-
-	var position = new THREE.Vector3();
-
-	var ParametricTube = function ( u, v, target ) {
-
-		v *= 2 * Math.PI;
-
-		var i = u * ( numpoints - 1 );
-		i = Math.floor( i );
-
-		path.getPointAt( u, position );
-
-		var normal = normals[ i ];
-		var binormal = binormals[ i ];
-
-		var cx = - scope.radius * Math.cos( v ); // TODO: Hack: Negating it so it faces outside.
-		var cy = scope.radius * Math.sin( v );
-
-		position.x += cx * normal.x + cy * binormal.x;
-		position.y += cx * normal.y + cy * binormal.y;
-		position.z += cx * normal.z + cy * binormal.z;
+		};
 
-		target.copy( position );
+		THREE.ParametricGeometry.call( this, ParametricTube, segments, segmentsRadius );
 
 	};
 
-	THREE.ParametricGeometry.call( this, ParametricTube, segments, segmentsRadius );
-
-};
-
-THREE.ParametricGeometries.TubeGeometry.prototype = Object.create( THREE.BufferGeometry.prototype );
-THREE.ParametricGeometries.TubeGeometry.prototype.constructor = THREE.ParametricGeometries.TubeGeometry;
-
+	ParametricGeometries.TubeGeometry.prototype = Object.create( THREE.BufferGeometry.prototype );
+	ParametricGeometries.TubeGeometry.prototype.constructor = ParametricGeometries.TubeGeometry;
+	/*********************************************
+	*
+	* Parametric Replacement for TorusKnotGeometry
+	*
+	*********************************************/
 
-/*********************************************
-  *
-  * Parametric Replacement for TorusKnotGeometry
-  *
-  *********************************************/
-THREE.ParametricGeometries.TorusKnotGeometry = function ( radius, tube, segmentsT, segmentsR, p, q ) {
+	ParametricGeometries.TorusKnotGeometry = function ( radius, tube, segmentsT, segmentsR, p, q ) {
 
-	this.radius = radius || 200;
-	this.tube = tube || 40;
-	this.segmentsT = segmentsT || 64;
-	this.segmentsR = segmentsR || 8;
-	this.p = p || 2;
-	this.q = q || 3;
+		this.radius = radius || 200;
+		this.tube = tube || 40;
+		this.segmentsT = segmentsT || 64;
+		this.segmentsR = segmentsR || 8;
+		this.p = p || 2;
+		this.q = q || 3;
 
-	function TorusKnotCurve() {
+		function TorusKnotCurve() {
 
-		THREE.Curve.call( this );
+			THREE.Curve.call( this );
 
-	}
-
-	TorusKnotCurve.prototype = Object.create( THREE.Curve.prototype );
-	TorusKnotCurve.prototype.constructor = TorusKnotCurve;
-
-	TorusKnotCurve.prototype.getPoint = function ( t, optionalTarget ) {
+		}
 
-		var point = optionalTarget || new THREE.Vector3();
+		TorusKnotCurve.prototype = Object.create( THREE.Curve.prototype );
+		TorusKnotCurve.prototype.constructor = TorusKnotCurve;
 
-		t *= Math.PI * 2;
+		TorusKnotCurve.prototype.getPoint = function ( t, optionalTarget ) {
 
-		var r = 0.5;
+			var point = optionalTarget || new THREE.Vector3();
+			t *= Math.PI * 2;
+			var r = 0.5;
+			var x = ( 1 + r * Math.cos( q * t ) ) * Math.cos( p * t );
+			var y = ( 1 + r * Math.cos( q * t ) ) * Math.sin( p * t );
+			var z = r * Math.sin( q * t );
+			return point.set( x, y, z ).multiplyScalar( radius );
 
-		var x = ( 1 + r * Math.cos( q * t ) ) * Math.cos( p * t );
-		var y = ( 1 + r * Math.cos( q * t ) ) * Math.sin( p * t );
-		var z = r * Math.sin( q * t );
+		};
 
-		return point.set( x, y, z ).multiplyScalar( radius );
+		var segments = segmentsT;
+		var radiusSegments = segmentsR;
+		var extrudePath = new TorusKnotCurve();
+		ParametricGeometries.TubeGeometry.call( this, extrudePath, segments, tube, radiusSegments, true, false );
 
 	};
 
-	var segments = segmentsT;
-	var radiusSegments = segmentsR;
-	var extrudePath = new TorusKnotCurve();
+	ParametricGeometries.TorusKnotGeometry.prototype = Object.create( THREE.BufferGeometry.prototype );
+	ParametricGeometries.TorusKnotGeometry.prototype.constructor = ParametricGeometries.TorusKnotGeometry;
+	/*********************************************
+	*
+	* Parametric Replacement for SphereGeometry
+	*
+	*********************************************/
 
-	THREE.ParametricGeometries.TubeGeometry.call( this, extrudePath, segments, tube, radiusSegments, true, false );
+	ParametricGeometries.SphereGeometry = function ( size, u, v ) {
 
-};
+		function sphere( u, v, target ) {
 
-THREE.ParametricGeometries.TorusKnotGeometry.prototype = Object.create( THREE.BufferGeometry.prototype );
-THREE.ParametricGeometries.TorusKnotGeometry.prototype.constructor = THREE.ParametricGeometries.TorusKnotGeometry;
-
-
-/*********************************************
-  *
-  * Parametric Replacement for SphereGeometry
-  *
-  *********************************************/
-THREE.ParametricGeometries.SphereGeometry = function ( size, u, v ) {
-
-	function sphere( u, v, target ) {
-
-		u *= Math.PI;
-		v *= 2 * Math.PI;
-
-		var x = size * Math.sin( u ) * Math.cos( v );
-		var y = size * Math.sin( u ) * Math.sin( v );
-		var z = size * Math.cos( u );
-
-		target.set( x, y, z );
-
-	}
+			u *= Math.PI;
+			v *= 2 * Math.PI;
+			var x = size * Math.sin( u ) * Math.cos( v );
+			var y = size * Math.sin( u ) * Math.sin( v );
+			var z = size * Math.cos( u );
+			target.set( x, y, z );
 
-	THREE.ParametricGeometry.call( this, sphere, u, v );
+		}
 
-};
+		THREE.ParametricGeometry.call( this, sphere, u, v );
 
-THREE.ParametricGeometries.SphereGeometry.prototype = Object.create( THREE.BufferGeometry.prototype );
-THREE.ParametricGeometries.SphereGeometry.prototype.constructor = THREE.ParametricGeometries.SphereGeometry;
+	};
 
+	ParametricGeometries.SphereGeometry.prototype = Object.create( THREE.BufferGeometry.prototype );
+	ParametricGeometries.SphereGeometry.prototype.constructor = ParametricGeometries.SphereGeometry;
+	/*********************************************
+	*
+	* Parametric Replacement for PlaneGeometry
+	*
+	*********************************************/
 
-/*********************************************
-  *
-  * Parametric Replacement for PlaneGeometry
-  *
-  *********************************************/
+	ParametricGeometries.PlaneGeometry = function ( width, depth, segmentsWidth, segmentsDepth ) {
 
-THREE.ParametricGeometries.PlaneGeometry = function ( width, depth, segmentsWidth, segmentsDepth ) {
+		function plane( u, v, target ) {
 
-	function plane( u, v, target ) {
+			var x = u * width;
+			var y = 0;
+			var z = v * depth;
+			target.set( x, y, z );
 
-		var x = u * width;
-		var y = 0;
-		var z = v * depth;
+		}
 
-		target.set( x, y, z );
+		THREE.ParametricGeometry.call( this, plane, segmentsWidth, segmentsDepth );
 
-	}
+	};
 
-	THREE.ParametricGeometry.call( this, plane, segmentsWidth, segmentsDepth );
+	ParametricGeometries.PlaneGeometry.prototype = Object.create( THREE.BufferGeometry.prototype );
+	ParametricGeometries.PlaneGeometry.prototype.constructor = ParametricGeometries.PlaneGeometry;
 
-};
+	THREE.ParametricGeometries = ParametricGeometries;
 
-THREE.ParametricGeometries.PlaneGeometry.prototype = Object.create( THREE.BufferGeometry.prototype );
-THREE.ParametricGeometries.PlaneGeometry.prototype.constructor = THREE.ParametricGeometries.PlaneGeometry;
+} )();

+ 137 - 0
examples/js/geometries/RoundedBoxGeometry.js

@@ -0,0 +1,137 @@
+( function () {
+
+	const tempNormal = new THREE.Vector3();
+
+	function getUv( faceDirVector, normal, uvAxis, projectionAxis, radius, sideLength ) {
+
+		const totArcLength = 2 * Math.PI * radius / 4; // length of the planes between the arcs on each axis
+
+		const centerLength = Math.max( sideLength - 2 * radius, 0 );
+		const halfArc = Math.PI / 4; // Get the vector projected onto the Y plane
+
+		tempNormal.copy( normal );
+		tempNormal[ projectionAxis ] = 0;
+		tempNormal.normalize(); // total amount of UV space alloted to a single arc
+
+		const arcUvRatio = 0.5 * totArcLength / ( totArcLength + centerLength ); // the distance along one arc the point is at
+
+		const arcAngleRatio = 1.0 - tempNormal.angleTo( faceDirVector ) / halfArc;
+
+		if ( Math.sign( tempNormal[ uvAxis ] ) === 1 ) {
+
+			return arcAngleRatio * arcUvRatio;
+
+		} else {
+
+			// total amount of UV space alloted to the plane between the arcs
+			const lenUv = centerLength / ( totArcLength + centerLength );
+			return lenUv + arcUvRatio + arcUvRatio * ( 1.0 - arcAngleRatio );
+
+		}
+
+	}
+
+	class RoundedBoxGeometry extends THREE.BoxGeometry {
+
+		constructor( width = 1, height = 1, depth = 1, segments = 2, radius = 0.1 ) {
+
+			// ensure segments is odd so we have a plane connecting the rounded corners
+			segments = segments * 2 + 1; // ensure radius isn't bigger than shortest side
+
+			radius = Math.min( width / 2, height / 2, depth / 2, radius );
+			super( 1, 1, 1, segments, segments, segments ); // if we just have one segment we're the same as a regular box
+
+			if ( segments === 1 ) return;
+			const geometry2 = this.toNonIndexed();
+			this.index = null;
+			this.attributes.position = geometry2.attributes.position;
+			this.attributes.normal = geometry2.attributes.normal;
+			this.attributes.uv = geometry2.attributes.uv; //
+
+			const position = new THREE.Vector3();
+			const normal = new THREE.Vector3();
+			const box = new THREE.Vector3( width, height, depth ).divideScalar( 2 ).subScalar( radius );
+			const positions = this.attributes.position.array;
+			const normals = this.attributes.normal.array;
+			const uvs = this.attributes.uv.array;
+			const faceTris = positions.length / 6;
+			const faceDirVector = new THREE.Vector3();
+			const halfSegmentSize = 0.5 / segments;
+
+			for ( let i = 0, j = 0; i < positions.length; i += 3, j += 2 ) {
+
+				position.fromArray( positions, i );
+				normal.copy( position );
+				normal.x -= Math.sign( normal.x ) * halfSegmentSize;
+				normal.y -= Math.sign( normal.y ) * halfSegmentSize;
+				normal.z -= Math.sign( normal.z ) * halfSegmentSize;
+				normal.normalize();
+				positions[ i + 0 ] = box.x * Math.sign( position.x ) + normal.x * radius;
+				positions[ i + 1 ] = box.y * Math.sign( position.y ) + normal.y * radius;
+				positions[ i + 2 ] = box.z * Math.sign( position.z ) + normal.z * radius;
+				normals[ i + 0 ] = normal.x;
+				normals[ i + 1 ] = normal.y;
+				normals[ i + 2 ] = normal.z;
+				const side = Math.floor( i / faceTris );
+
+				switch ( side ) {
+
+					case 0:
+					// right
+					// generate UVs along Z then Y
+						faceDirVector.set( 1, 0, 0 );
+						uvs[ j + 0 ] = getUv( faceDirVector, normal, 'z', 'y', radius, depth );
+						uvs[ j + 1 ] = 1.0 - getUv( faceDirVector, normal, 'y', 'z', radius, height );
+						break;
+
+					case 1:
+					// left
+					// generate UVs along Z then Y
+						faceDirVector.set( - 1, 0, 0 );
+						uvs[ j + 0 ] = 1.0 - getUv( faceDirVector, normal, 'z', 'y', radius, depth );
+						uvs[ j + 1 ] = 1.0 - getUv( faceDirVector, normal, 'y', 'z', radius, height );
+						break;
+
+					case 2:
+					// top
+					// generate UVs along X then Z
+						faceDirVector.set( 0, 1, 0 );
+						uvs[ j + 0 ] = 1.0 - getUv( faceDirVector, normal, 'x', 'z', radius, width );
+						uvs[ j + 1 ] = getUv( faceDirVector, normal, 'z', 'x', radius, depth );
+						break;
+
+					case 3:
+					// bottom
+					// generate UVs along X then Z
+						faceDirVector.set( 0, - 1, 0 );
+						uvs[ j + 0 ] = 1.0 - getUv( faceDirVector, normal, 'x', 'z', radius, width );
+						uvs[ j + 1 ] = 1.0 - getUv( faceDirVector, normal, 'z', 'x', radius, depth );
+						break;
+
+					case 4:
+					// front
+					// generate UVs along X then Y
+						faceDirVector.set( 0, 0, 1 );
+						uvs[ j + 0 ] = 1.0 - getUv( faceDirVector, normal, 'x', 'y', radius, width );
+						uvs[ j + 1 ] = 1.0 - getUv( faceDirVector, normal, 'y', 'x', radius, height );
+						break;
+
+					case 5:
+					// back
+					// generate UVs along X then Y
+						faceDirVector.set( 0, 0, - 1 );
+						uvs[ j + 0 ] = getUv( faceDirVector, normal, 'x', 'y', radius, width );
+						uvs[ j + 1 ] = 1.0 - getUv( faceDirVector, normal, 'y', 'x', radius, height );
+						break;
+
+				}
+
+			}
+
+		}
+
+	}
+
+	THREE.RoundedBoxGeometry = RoundedBoxGeometry;
+
+} )();

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 36 - 474
examples/js/geometries/TeapotGeometry.js


+ 49 - 0
examples/js/helpers/LightProbeHelper.js

@@ -0,0 +1,49 @@
+( function () {
+
+	class LightProbeHelper extends THREE.Mesh {
+
+		constructor( lightProbe, size ) {
+
+			const material = new THREE.ShaderMaterial( {
+				type: 'LightProbeHelperMaterial',
+				uniforms: {
+					sh: {
+						value: lightProbe.sh.coefficients
+					},
+					// by reference
+					intensity: {
+						value: lightProbe.intensity
+					}
+				},
+				vertexShader: [ 'varying vec3 vNormal;', 'void main() {', '	vNormal = normalize( normalMatrix * normal );', '	gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );', '}' ].join( '\n' ),
+				fragmentShader: [ '#define RECIPROCAL_PI 0.318309886', 'vec3 inverseTransformDirection( in vec3 normal, in mat4 matrix ) {', '	// matrix is assumed to be orthogonal', '	return normalize( ( vec4( normal, 0.0 ) * matrix ).xyz );', '}', '// source: https://graphics.stanford.edu/papers/envmap/envmap.pdf', 'vec3 shGetIrradianceAt( in vec3 normal, in vec3 shCoefficients[ 9 ] ) {', '	// normal is assumed to have unit length', '	float x = normal.x, y = normal.y, z = normal.z;', '	// band 0', '	vec3 result = shCoefficients[ 0 ] * 0.886227;', '	// band 1', '	result += shCoefficients[ 1 ] * 2.0 * 0.511664 * y;', '	result += shCoefficients[ 2 ] * 2.0 * 0.511664 * z;', '	result += shCoefficients[ 3 ] * 2.0 * 0.511664 * x;', '	// band 2', '	result += shCoefficients[ 4 ] * 2.0 * 0.429043 * x * y;', '	result += shCoefficients[ 5 ] * 2.0 * 0.429043 * y * z;', '	result += shCoefficients[ 6 ] * ( 0.743125 * z * z - 0.247708 );', '	result += shCoefficients[ 7 ] * 2.0 * 0.429043 * x * z;', '	result += shCoefficients[ 8 ] * 0.429043 * ( x * x - y * y );', '	return result;', '}', 'uniform vec3 sh[ 9 ]; // sh coefficients', 'uniform float intensity; // light probe intensity', 'varying vec3 vNormal;', 'void main() {', '	vec3 normal = normalize( vNormal );', '	vec3 worldNormal = inverseTransformDirection( normal, viewMatrix );', '	vec3 irradiance = shGetIrradianceAt( worldNormal, sh );', '	vec3 outgoingLight = RECIPROCAL_PI * irradiance * intensity;', '	gl_FragColor = linearToOutputTexel( vec4( outgoingLight, 1.0 ) );', '}' ].join( '\n' )
+			} );
+			const geometry = new THREE.SphereGeometry( 1, 32, 16 );
+			super( geometry, material );
+			this.lightProbe = lightProbe;
+			this.size = size;
+			this.type = 'LightProbeHelper';
+			this.onBeforeRender();
+
+		}
+
+		dispose() {
+
+			this.geometry.dispose();
+			this.material.dispose();
+
+		}
+
+		onBeforeRender() {
+
+			this.position.copy( this.lightProbe.position );
+			this.scale.set( 1, 1, 1 ).multiplyScalar( this.size );
+			this.material.uniforms.intensity.value = this.lightProbe.intensity;
+
+		}
+
+	}
+
+	THREE.LightProbeHelper = LightProbeHelper;
+
+} )();

+ 89 - 0
examples/js/helpers/PositionalAudioHelper.js

@@ -0,0 +1,89 @@
+( function () {
+
+	class PositionalAudioHelper extends THREE.Line {
+
+		constructor( audio, range = 1, divisionsInnerAngle = 16, divisionsOuterAngle = 2 ) {
+
+			const geometry = new THREE.BufferGeometry();
+			const divisions = divisionsInnerAngle + divisionsOuterAngle * 2;
+			const positions = new Float32Array( ( divisions * 3 + 3 ) * 3 );
+			geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
+			const materialInnerAngle = new THREE.LineBasicMaterial( {
+				color: 0x00ff00
+			} );
+			const materialOuterAngle = new THREE.LineBasicMaterial( {
+				color: 0xffff00
+			} );
+			super( geometry, [ materialOuterAngle, materialInnerAngle ] );
+			this.audio = audio;
+			this.range = range;
+			this.divisionsInnerAngle = divisionsInnerAngle;
+			this.divisionsOuterAngle = divisionsOuterAngle;
+			this.type = 'PositionalAudioHelper';
+			this.update();
+
+		}
+
+		update() {
+
+			const audio = this.audio;
+			const range = this.range;
+			const divisionsInnerAngle = this.divisionsInnerAngle;
+			const divisionsOuterAngle = this.divisionsOuterAngle;
+			const coneInnerAngle = THREE.MathUtils.degToRad( audio.panner.coneInnerAngle );
+			const coneOuterAngle = THREE.MathUtils.degToRad( audio.panner.coneOuterAngle );
+			const halfConeInnerAngle = coneInnerAngle / 2;
+			const halfConeOuterAngle = coneOuterAngle / 2;
+			let start = 0;
+			let count = 0;
+			let i;
+			let stride;
+			const geometry = this.geometry;
+			const positionAttribute = geometry.attributes.position;
+			geometry.clearGroups(); //
+
+			function generateSegment( from, to, divisions, materialIndex ) {
+
+				const step = ( to - from ) / divisions;
+				positionAttribute.setXYZ( start, 0, 0, 0 );
+				count ++;
+
+				for ( i = from; i < to; i += step ) {
+
+					stride = start + count;
+					positionAttribute.setXYZ( stride, Math.sin( i ) * range, 0, Math.cos( i ) * range );
+					positionAttribute.setXYZ( stride + 1, Math.sin( Math.min( i + step, to ) ) * range, 0, Math.cos( Math.min( i + step, to ) ) * range );
+					positionAttribute.setXYZ( stride + 2, 0, 0, 0 );
+					count += 3;
+
+				}
+
+				geometry.addGroup( start, count, materialIndex );
+				start += count;
+				count = 0;
+
+			} //
+
+
+			generateSegment( - halfConeOuterAngle, - halfConeInnerAngle, divisionsOuterAngle, 0 );
+			generateSegment( - halfConeInnerAngle, halfConeInnerAngle, divisionsInnerAngle, 1 );
+			generateSegment( halfConeInnerAngle, halfConeOuterAngle, divisionsOuterAngle, 0 ); //
+
+			positionAttribute.needsUpdate = true;
+			if ( coneInnerAngle === coneOuterAngle ) this.material[ 0 ].visible = false;
+
+		}
+
+		dispose() {
+
+			this.geometry.dispose();
+			this.material[ 0 ].dispose();
+			this.material[ 1 ].dispose();
+
+		}
+
+	}
+
+	THREE.PositionalAudioHelper = PositionalAudioHelper;
+
+} )();

+ 73 - 0
examples/js/helpers/RectAreaLightHelper.js

@@ -0,0 +1,73 @@
+( function () {
+
+	/**
+ *	This helper must be added as a child of the light
+ */
+
+	class RectAreaLightHelper extends THREE.Line {
+
+		constructor( light, color ) {
+
+			const positions = [ 1, 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, - 1, 0, 1, 1, 0 ];
+			const geometry = new THREE.BufferGeometry();
+			geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
+			geometry.computeBoundingSphere();
+			const material = new THREE.LineBasicMaterial( {
+				fog: false
+			} );
+			super( geometry, material );
+			this.light = light;
+			this.color = color; // optional hardwired color for the helper
+
+			this.type = 'RectAreaLightHelper'; //
+
+			const positions2 = [ 1, 1, 0, - 1, 1, 0, - 1, - 1, 0, 1, 1, 0, - 1, - 1, 0, 1, - 1, 0 ];
+			const geometry2 = new THREE.BufferGeometry();
+			geometry2.setAttribute( 'position', new THREE.Float32BufferAttribute( positions2, 3 ) );
+			geometry2.computeBoundingSphere();
+			this.add( new THREE.Mesh( geometry2, new THREE.MeshBasicMaterial( {
+				side: THREE.BackSide,
+				fog: false
+			} ) ) );
+
+		}
+
+		updateMatrixWorld() {
+
+			this.scale.set( 0.5 * this.light.width, 0.5 * this.light.height, 1 );
+
+			if ( this.color !== undefined ) {
+
+				this.material.color.set( this.color );
+				this.children[ 0 ].material.color.set( this.color );
+
+			} else {
+
+				this.material.color.copy( this.light.color ).multiplyScalar( this.light.intensity ); // prevent hue shift
+
+				const c = this.material.color;
+				const max = Math.max( c.r, c.g, c.b );
+				if ( max > 1 ) c.multiplyScalar( 1 / max );
+				this.children[ 0 ].material.color.copy( this.material.color );
+
+			}
+
+			this.matrixWorld.copy( this.light.matrixWorld ).scale( this.scale );
+			this.children[ 0 ].matrixWorld.copy( this.matrixWorld );
+
+		}
+
+		dispose() {
+
+			this.geometry.dispose();
+			this.material.dispose();
+			this.children[ 0 ].geometry.dispose();
+			this.children[ 0 ].material.dispose();
+
+		}
+
+	}
+
+	THREE.RectAreaLightHelper = RectAreaLightHelper;
+
+} )();

+ 91 - 0
examples/js/helpers/VertexNormalsHelper.js

@@ -0,0 +1,91 @@
+( function () {
+
+	const _v1 = new THREE.Vector3();
+
+	const _v2 = new THREE.Vector3();
+
+	const _normalMatrix = new THREE.Matrix3();
+
+	class VertexNormalsHelper extends THREE.LineSegments {
+
+		constructor( object, size = 1, color = 0xff0000 ) {
+
+			let nNormals = 0;
+			const objGeometry = object.geometry;
+
+			if ( objGeometry && objGeometry.isGeometry ) {
+
+				console.error( 'THREE.VertexNormalsHelper no longer supports Geometry. Use THREE.BufferGeometry instead.' );
+				return;
+
+			} else if ( objGeometry && objGeometry.isBufferGeometry ) {
+
+				nNormals = objGeometry.attributes.normal.count;
+
+			} //
+
+
+			const geometry = new THREE.BufferGeometry();
+			const positions = new THREE.Float32BufferAttribute( nNormals * 2 * 3, 3 );
+			geometry.setAttribute( 'position', positions );
+			super( geometry, new THREE.LineBasicMaterial( {
+				color,
+				toneMapped: false
+			} ) );
+			this.object = object;
+			this.size = size;
+			this.type = 'VertexNormalsHelper'; //
+
+			this.matrixAutoUpdate = false;
+			this.update();
+
+		}
+
+		update() {
+
+			this.object.updateMatrixWorld( true );
+
+			_normalMatrix.getNormalMatrix( this.object.matrixWorld );
+
+			const matrixWorld = this.object.matrixWorld;
+			const position = this.geometry.attributes.position; //
+
+			const objGeometry = this.object.geometry;
+
+			if ( objGeometry && objGeometry.isGeometry ) {
+
+				console.error( 'THREE.VertexNormalsHelper no longer supports Geometry. Use THREE.BufferGeometry instead.' );
+				return;
+
+			} else if ( objGeometry && objGeometry.isBufferGeometry ) {
+
+				const objPos = objGeometry.attributes.position;
+				const objNorm = objGeometry.attributes.normal;
+				let idx = 0; // for simplicity, ignore index and drawcalls, and render every normal
+
+				for ( let j = 0, jl = objPos.count; j < jl; j ++ ) {
+
+					_v1.set( objPos.getX( j ), objPos.getY( j ), objPos.getZ( j ) ).applyMatrix4( matrixWorld );
+
+					_v2.set( objNorm.getX( j ), objNorm.getY( j ), objNorm.getZ( j ) );
+
+					_v2.applyMatrix3( _normalMatrix ).normalize().multiplyScalar( this.size ).add( _v1 );
+
+					position.setXYZ( idx, _v1.x, _v1.y, _v1.z );
+					idx = idx + 1;
+					position.setXYZ( idx, _v2.x, _v2.y, _v2.z );
+					idx = idx + 1;
+
+				}
+
+			}
+
+			position.needsUpdate = true;
+
+		}
+
+	}
+
+	THREE.VertexNormalsHelper = VertexNormalsHelper;
+
+} )();

+ 72 - 0
examples/js/helpers/VertexTangentsHelper.js

@@ -0,0 +1,72 @@
+( function () {
+
+	const _v1 = new THREE.Vector3();
+
+	const _v2 = new THREE.Vector3();
+
+	class VertexTangentsHelper extends THREE.LineSegments {
+
+		constructor( object, size = 1, color = 0x00ffff ) {
+
+			const objGeometry = object.geometry;
+
+			if ( ! ( objGeometry && objGeometry.isBufferGeometry ) ) {
+
+				console.error( 'THREE.VertexTangentsHelper: geometry not an instance of THREE.BufferGeometry.', objGeometry );
+				return;
+
+			}
+
+			const nTangents = objGeometry.attributes.tangent.count; //
+
+			const geometry = new THREE.BufferGeometry();
+			const positions = new THREE.Float32BufferAttribute( nTangents * 2 * 3, 3 );
+			geometry.setAttribute( 'position', positions );
+			super( geometry, new THREE.LineBasicMaterial( {
+				color,
+				toneMapped: false
+			} ) );
+			this.object = object;
+			this.size = size;
+			this.type = 'VertexTangentsHelper'; //
+
+			this.matrixAutoUpdate = false;
+			this.update();
+
+		}
+
+		update() {
+
+			this.object.updateMatrixWorld( true );
+			const matrixWorld = this.object.matrixWorld;
+			const position = this.geometry.attributes.position; //
+
+			const objGeometry = this.object.geometry;
+			const objPos = objGeometry.attributes.position;
+			const objTan = objGeometry.attributes.tangent;
+			let idx = 0; // for simplicity, ignore index and drawcalls, and render every tangent
+
+			for ( let j = 0, jl = objPos.count; j < jl; j ++ ) {
+
+				_v1.set( objPos.getX( j ), objPos.getY( j ), objPos.getZ( j ) ).applyMatrix4( matrixWorld );
+
+				_v2.set( objTan.getX( j ), objTan.getY( j ), objTan.getZ( j ) );
+
+				_v2.transformDirection( matrixWorld ).multiplyScalar( this.size ).add( _v1 );
+
+				position.setXYZ( idx, _v1.x, _v1.y, _v1.z );
+				idx = idx + 1;
+				position.setXYZ( idx, _v2.x, _v2.y, _v2.z );
+				idx = idx + 1;
+
+			}
+
+			position.needsUpdate = true;
+
+		}
+
+	}
+
+	THREE.VertexTangentsHelper = VertexTangentsHelper;
+
+} )();

+ 141 - 157
examples/js/interactive/SelectionBox.js

@@ -1,197 +1,181 @@
-/**
+( function () {
+
+	/**
  * This is a class to check whether objects are in a selection area in 3D space
  */
 
-THREE.SelectionBox = ( function () {
-
-	var frustum = new THREE.Frustum();
-	var center = new THREE.Vector3();
-
-	var tmpPoint = new THREE.Vector3();
-
-	var vecNear = new THREE.Vector3();
-	var vecTopLeft = new THREE.Vector3();
-	var vecTopRight = new THREE.Vector3();
-	var vecDownRight = new THREE.Vector3();
-	var vecDownLeft = new THREE.Vector3();
-
-	var vecFarTopLeft = new THREE.Vector3();
-	var vecFarTopRight = new THREE.Vector3();
-	var vecFarDownRight = new THREE.Vector3();
-	var vecFarDownLeft = new THREE.Vector3();
-
-	var vectemp1 = new THREE.Vector3();
-	var vectemp2 = new THREE.Vector3();
-	var vectemp3 = new THREE.Vector3();
-
-	function SelectionBox( camera, scene, deep ) {
-
-		this.camera = camera;
-		this.scene = scene;
-		this.startPoint = new THREE.Vector3();
-		this.endPoint = new THREE.Vector3();
-		this.collection = [];
-		this.deep = deep || Number.MAX_VALUE;
-
-	}
-
-	SelectionBox.prototype.select = function ( startPoint, endPoint ) {
-
-		this.startPoint = startPoint || this.startPoint;
-		this.endPoint = endPoint || this.endPoint;
-		this.collection = [];
-
-		this.updateFrustum( this.startPoint, this.endPoint );
-		this.searchChildInFrustum( frustum, this.scene );
+	var SelectionBox = function () {
+
+		var frustum = new THREE.Frustum();
+		var center = new THREE.Vector3();
+		var tmpPoint = new THREE.Vector3();
+		var vecNear = new THREE.Vector3();
+		var vecTopLeft = new THREE.Vector3();
+		var vecTopRight = new THREE.Vector3();
+		var vecDownRight = new THREE.Vector3();
+		var vecDownLeft = new THREE.Vector3();
+		var vecFarTopLeft = new THREE.Vector3();
+		var vecFarTopRight = new THREE.Vector3();
+		var vecFarDownRight = new THREE.Vector3();
+		var vecFarDownLeft = new THREE.Vector3();
+		var vectemp1 = new THREE.Vector3();
+		var vectemp2 = new THREE.Vector3();
+		var vectemp3 = new THREE.Vector3();
+
+		function SelectionBox( camera, scene, deep ) {
+
+			this.camera = camera;
+			this.scene = scene;
+			this.startPoint = new THREE.Vector3();
+			this.endPoint = new THREE.Vector3();
+			this.collection = [];
+			this.deep = deep || Number.MAX_VALUE;
 
-		return this.collection;
+		}
 
-	};
+		SelectionBox.prototype.select = function ( startPoint, endPoint ) {
 
-	SelectionBox.prototype.updateFrustum = function ( startPoint, endPoint ) {
+			this.startPoint = startPoint || this.startPoint;
+			this.endPoint = endPoint || this.endPoint;
+			this.collection = [];
+			this.updateFrustum( this.startPoint, this.endPoint );
+			this.searchChildInFrustum( frustum, this.scene );
+			return this.collection;
 
-		startPoint = startPoint || this.startPoint;
-		endPoint = endPoint || this.endPoint;
+		};
 
-		// Avoid invalid frustum
+		SelectionBox.prototype.updateFrustum = function ( startPoint, endPoint ) {
 
-		if ( startPoint.x === endPoint.x ) {
+			startPoint = startPoint || this.startPoint;
+			endPoint = endPoint || this.endPoint; // Avoid invalid frustum
 
-			endPoint.x += Number.EPSILON;
+			if ( startPoint.x === endPoint.x ) {
 
-		}
+				endPoint.x += Number.EPSILON;
 
-		if ( startPoint.y === endPoint.y ) {
+			}
 
-			endPoint.y += Number.EPSILON;
+			if ( startPoint.y === endPoint.y ) {
 
-		}
+				endPoint.y += Number.EPSILON;
 
-		this.camera.updateProjectionMatrix();
-		this.camera.updateMatrixWorld();
-
-		if ( this.camera.isPerspectiveCamera ) {
-
-			tmpPoint.copy( startPoint );
-			tmpPoint.x = Math.min( startPoint.x, endPoint.x );
-			tmpPoint.y = Math.max( startPoint.y, endPoint.y );
-			endPoint.x = Math.max( startPoint.x, endPoint.x );
-			endPoint.y = Math.min( startPoint.y, endPoint.y );
-
-			vecNear.setFromMatrixPosition( this.camera.matrixWorld );
-			vecTopLeft.copy( tmpPoint );
-			vecTopRight.set( endPoint.x, tmpPoint.y, 0 );
-			vecDownRight.copy( endPoint );
-			vecDownLeft.set( tmpPoint.x, endPoint.y, 0 );
-
-			vecTopLeft.unproject( this.camera );
-			vecTopRight.unproject( this.camera );
-			vecDownRight.unproject( this.camera );
-			vecDownLeft.unproject( this.camera );
-
-			vectemp1.copy( vecTopLeft ).sub( vecNear );
-			vectemp2.copy( vecTopRight ).sub( vecNear );
-			vectemp3.copy( vecDownRight ).sub( vecNear );
-			vectemp1.normalize();
-			vectemp2.normalize();
-			vectemp3.normalize();
-
-			vectemp1.multiplyScalar( this.deep );
-			vectemp2.multiplyScalar( this.deep );
-			vectemp3.multiplyScalar( this.deep );
-			vectemp1.add( vecNear );
-			vectemp2.add( vecNear );
-			vectemp3.add( vecNear );
-
-			var planes = frustum.planes;
-
-			planes[ 0 ].setFromCoplanarPoints( vecNear, vecTopLeft, vecTopRight );
-			planes[ 1 ].setFromCoplanarPoints( vecNear, vecTopRight, vecDownRight );
-			planes[ 2 ].setFromCoplanarPoints( vecDownRight, vecDownLeft, vecNear );
-			planes[ 3 ].setFromCoplanarPoints( vecDownLeft, vecTopLeft, vecNear );
-			planes[ 4 ].setFromCoplanarPoints( vecTopRight, vecDownRight, vecDownLeft );
-			planes[ 5 ].setFromCoplanarPoints( vectemp3, vectemp2, vectemp1 );
-			planes[ 5 ].normal.multiplyScalar( - 1 );
-
-		} else if ( this.camera.isOrthographicCamera ) {
-
-			var left = Math.min( startPoint.x, endPoint.x );
-			var top = Math.max( startPoint.y, endPoint.y );
-			var right = Math.max( startPoint.x, endPoint.x );
-			var down = Math.min( startPoint.y, endPoint.y );
-
-			vecTopLeft.set( left, top, - 1 );
-			vecTopRight.set( right, top, - 1 );
-			vecDownRight.set( right, down, - 1 );
-			vecDownLeft.set( left, down, - 1 );
-
-			vecFarTopLeft.set( left, top, 1 );
-			vecFarTopRight.set( right, top, 1 );
-			vecFarDownRight.set( right, down, 1 );
-			vecFarDownLeft.set( left, down, 1 );
-
-			vecTopLeft.unproject( this.camera );
-			vecTopRight.unproject( this.camera );
-			vecDownRight.unproject( this.camera );
-			vecDownLeft.unproject( this.camera );
-
-			vecFarTopLeft.unproject( this.camera );
-			vecFarTopRight.unproject( this.camera );
-			vecFarDownRight.unproject( this.camera );
-			vecFarDownLeft.unproject( this.camera );
-
-			var planes = frustum.planes;
-
-			planes[ 0 ].setFromCoplanarPoints( vecTopLeft, vecFarTopLeft, vecFarTopRight );
-			planes[ 1 ].setFromCoplanarPoints( vecTopRight, vecFarTopRight, vecFarDownRight );
-			planes[ 2 ].setFromCoplanarPoints( vecFarDownRight, vecFarDownLeft, vecDownLeft );
-			planes[ 3 ].setFromCoplanarPoints( vecFarDownLeft, vecFarTopLeft, vecTopLeft );
-			planes[ 4 ].setFromCoplanarPoints( vecTopRight, vecDownRight, vecDownLeft );
-			planes[ 5 ].setFromCoplanarPoints( vecFarDownRight, vecFarTopRight, vecFarTopLeft );
-			planes[ 5 ].normal.multiplyScalar( - 1 );
-
-		} else {
-
-			console.error( 'THREE.SelectionBox: Unsupported camera type.' );
+			}
 
-		}
+			this.camera.updateProjectionMatrix();
+			this.camera.updateMatrixWorld();
+
+			if ( this.camera.isPerspectiveCamera ) {
+
+				tmpPoint.copy( startPoint );
+				tmpPoint.x = Math.min( startPoint.x, endPoint.x );
+				tmpPoint.y = Math.max( startPoint.y, endPoint.y );
+				endPoint.x = Math.max( startPoint.x, endPoint.x );
+				endPoint.y = Math.min( startPoint.y, endPoint.y );
+				vecNear.setFromMatrixPosition( this.camera.matrixWorld );
+				vecTopLeft.copy( tmpPoint );
+				vecTopRight.set( endPoint.x, tmpPoint.y, 0 );
+				vecDownRight.copy( endPoint );
+				vecDownLeft.set( tmpPoint.x, endPoint.y, 0 );
+				vecTopLeft.unproject( this.camera );
+				vecTopRight.unproject( this.camera );
+				vecDownRight.unproject( this.camera );
+				vecDownLeft.unproject( this.camera );
+				vectemp1.copy( vecTopLeft ).sub( vecNear );
+				vectemp2.copy( vecTopRight ).sub( vecNear );
+				vectemp3.copy( vecDownRight ).sub( vecNear );
+				vectemp1.normalize();
+				vectemp2.normalize();
+				vectemp3.normalize();
+				vectemp1.multiplyScalar( this.deep );
+				vectemp2.multiplyScalar( this.deep );
+				vectemp3.multiplyScalar( this.deep );
+				vectemp1.add( vecNear );
+				vectemp2.add( vecNear );
+				vectemp3.add( vecNear );
+				var planes = frustum.planes;
+				planes[ 0 ].setFromCoplanarPoints( vecNear, vecTopLeft, vecTopRight );
+				planes[ 1 ].setFromCoplanarPoints( vecNear, vecTopRight, vecDownRight );
+				planes[ 2 ].setFromCoplanarPoints( vecDownRight, vecDownLeft, vecNear );
+				planes[ 3 ].setFromCoplanarPoints( vecDownLeft, vecTopLeft, vecNear );
+				planes[ 4 ].setFromCoplanarPoints( vecTopRight, vecDownRight, vecDownLeft );
+				planes[ 5 ].setFromCoplanarPoints( vectemp3, vectemp2, vectemp1 );
+				planes[ 5 ].normal.multiplyScalar( - 1 );
+
+			} else if ( this.camera.isOrthographicCamera ) {
+
+				var left = Math.min( startPoint.x, endPoint.x );
+				var top = Math.max( startPoint.y, endPoint.y );
+				var right = Math.max( startPoint.x, endPoint.x );
+				var down = Math.min( startPoint.y, endPoint.y );
+				vecTopLeft.set( left, top, - 1 );
+				vecTopRight.set( right, top, - 1 );
+				vecDownRight.set( right, down, - 1 );
+				vecDownLeft.set( left, down, - 1 );
+				vecFarTopLeft.set( left, top, 1 );
+				vecFarTopRight.set( right, top, 1 );
+				vecFarDownRight.set( right, down, 1 );
+				vecFarDownLeft.set( left, down, 1 );
+				vecTopLeft.unproject( this.camera );
+				vecTopRight.unproject( this.camera );
+				vecDownRight.unproject( this.camera );
+				vecDownLeft.unproject( this.camera );
+				vecFarTopLeft.unproject( this.camera );
+				vecFarTopRight.unproject( this.camera );
+				vecFarDownRight.unproject( this.camera );
+				vecFarDownLeft.unproject( this.camera );
+				var planes = frustum.planes;
+				planes[ 0 ].setFromCoplanarPoints( vecTopLeft, vecFarTopLeft, vecFarTopRight );
+				planes[ 1 ].setFromCoplanarPoints( vecTopRight, vecFarTopRight, vecFarDownRight );
+				planes[ 2 ].setFromCoplanarPoints( vecFarDownRight, vecFarDownLeft, vecDownLeft );
+				planes[ 3 ].setFromCoplanarPoints( vecFarDownLeft, vecFarTopLeft, vecTopLeft );
+				planes[ 4 ].setFromCoplanarPoints( vecTopRight, vecDownRight, vecDownLeft );
+				planes[ 5 ].setFromCoplanarPoints( vecFarDownRight, vecFarTopRight, vecFarTopLeft );
+				planes[ 5 ].normal.multiplyScalar( - 1 );
+
+			} else {
+
+				console.error( 'THREE.SelectionBox: Unsupported camera type.' );
 
-	};
+			}
 
-	SelectionBox.prototype.searchChildInFrustum = function ( frustum, object ) {
+		};
 
-		if ( object.isMesh || object.isLine || object.isPoints ) {
+		SelectionBox.prototype.searchChildInFrustum = function ( frustum, object ) {
 
-			if ( object.material !== undefined ) {
+			if ( object.isMesh || object.isLine || object.isPoints ) {
 
-				if ( object.geometry.boundingSphere === null ) object.geometry.computeBoundingSphere();
+				if ( object.material !== undefined ) {
 
-				center.copy( object.geometry.boundingSphere.center );
+					if ( object.geometry.boundingSphere === null ) object.geometry.computeBoundingSphere();
+					center.copy( object.geometry.boundingSphere.center );
+					center.applyMatrix4( object.matrixWorld );
 
-				center.applyMatrix4( object.matrixWorld );
+					if ( frustum.containsPoint( center ) ) {
 
-				if ( frustum.containsPoint( center ) ) {
+						this.collection.push( object );
 
-					this.collection.push( object );
+					}
 
 				}
 
 			}
 
-		}
+			if ( object.children.length > 0 ) {
 
-		if ( object.children.length > 0 ) {
+				for ( var x = 0; x < object.children.length; x ++ ) {
 
-			for ( var x = 0; x < object.children.length; x ++ ) {
+					this.searchChildInFrustum( frustum, object.children[ x ] );
 
-				this.searchChildInFrustum( frustum, object.children[ x ] );
+				}
 
 			}
 
-		}
+		};
+
+		return SelectionBox;
 
-	};
+	}();
 
-	return SelectionBox;
+	THREE.SelectionBox = SelectionBox;
 
 } )();

+ 50 - 53
examples/js/interactive/SelectionHelper.js

@@ -1,79 +1,76 @@
-THREE.SelectionHelper = ( function () {
+( function () {
 
-	function SelectionHelper( selectionBox, renderer, cssClassName ) {
+	var SelectionHelper = function () {
 
-		this.element = document.createElement( 'div' );
-		this.element.classList.add( cssClassName );
-		this.element.style.pointerEvents = 'none';
+		function SelectionHelper( selectionBox, renderer, cssClassName ) {
 
-		this.renderer = renderer;
-
-		this.startPoint = new THREE.Vector2();
-		this.pointTopLeft = new THREE.Vector2();
-		this.pointBottomRight = new THREE.Vector2();
-
-		this.isDown = false;
-
-		this.renderer.domElement.addEventListener( 'pointerdown', function ( event ) {
-
-			this.isDown = true;
-			this.onSelectStart( event );
-
-		}.bind( this ) );
-
-		this.renderer.domElement.addEventListener( 'pointermove', function ( event ) {
+			this.element = document.createElement( 'div' );
+			this.element.classList.add( cssClassName );
+			this.element.style.pointerEvents = 'none';
+			this.renderer = renderer;
+			this.startPoint = new THREE.Vector2();
+			this.pointTopLeft = new THREE.Vector2();
+			this.pointBottomRight = new THREE.Vector2();
+			this.isDown = false;
+			this.renderer.domElement.addEventListener( 'pointerdown', function ( event ) {
 
-			if ( this.isDown ) {
+				this.isDown = true;
+				this.onSelectStart( event );
 
-				this.onSelectMove( event );
+			}.bind( this ) );
+			this.renderer.domElement.addEventListener( 'pointermove', function ( event ) {
 
-			}
+				if ( this.isDown ) {
 
-		}.bind( this ) );
+					this.onSelectMove( event );
 
-		this.renderer.domElement.addEventListener( 'pointerup', function ( event ) {
+				}
 
-			this.isDown = false;
-			this.onSelectOver( event );
+			}.bind( this ) );
+			this.renderer.domElement.addEventListener( 'pointerup', function ( event ) {
 
-		}.bind( this ) );
+				this.isDown = false;
+				this.onSelectOver( event );
 
-	}
+			}.bind( this ) );
 
-	SelectionHelper.prototype.onSelectStart = function ( event ) {
+		}
 
-		this.renderer.domElement.parentElement.appendChild( this.element );
+		SelectionHelper.prototype.onSelectStart = function ( event ) {
 
-		this.element.style.left = event.clientX + 'px';
-		this.element.style.top = event.clientY + 'px';
-		this.element.style.width = '0px';
-		this.element.style.height = '0px';
+			this.renderer.domElement.parentElement.appendChild( this.element );
+			this.element.style.left = event.clientX + 'px';
+			this.element.style.top = event.clientY + 'px';
+			this.element.style.width = '0px';
+			this.element.style.height = '0px';
+			this.startPoint.x = event.clientX;
+			this.startPoint.y = event.clientY;
 
-		this.startPoint.x = event.clientX;
-		this.startPoint.y = event.clientY;
+		};
 
-	};
+		SelectionHelper.prototype.onSelectMove = function ( event ) {
 
-	SelectionHelper.prototype.onSelectMove = function ( event ) {
+			this.pointBottomRight.x = Math.max( this.startPoint.x, event.clientX );
+			this.pointBottomRight.y = Math.max( this.startPoint.y, event.clientY );
+			this.pointTopLeft.x = Math.min( this.startPoint.x, event.clientX );
+			this.pointTopLeft.y = Math.min( this.startPoint.y, event.clientY );
+			this.element.style.left = this.pointTopLeft.x + 'px';
+			this.element.style.top = this.pointTopLeft.y + 'px';
+			this.element.style.width = this.pointBottomRight.x - this.pointTopLeft.x + 'px';
+			this.element.style.height = this.pointBottomRight.y - this.pointTopLeft.y + 'px';
 
-		this.pointBottomRight.x = Math.max( this.startPoint.x, event.clientX );
-		this.pointBottomRight.y = Math.max( this.startPoint.y, event.clientY );
-		this.pointTopLeft.x = Math.min( this.startPoint.x, event.clientX );
-		this.pointTopLeft.y = Math.min( this.startPoint.y, event.clientY );
+		};
 
-		this.element.style.left = this.pointTopLeft.x + 'px';
-		this.element.style.top = this.pointTopLeft.y + 'px';
-		this.element.style.width = ( this.pointBottomRight.x - this.pointTopLeft.x ) + 'px';
-		this.element.style.height = ( this.pointBottomRight.y - this.pointTopLeft.y ) + 'px';
+		SelectionHelper.prototype.onSelectOver = function () {
 
-	};
+			this.element.parentElement.removeChild( this.element );
 
-	SelectionHelper.prototype.onSelectOver = function () {
+		};
 
-		this.element.parentElement.removeChild( this.element );
+		return SelectionHelper;
 
-	};
+	}();
 
-	return SelectionHelper;
+	THREE.SelectionHelper = SelectionHelper;
 
 } )();

+ 150 - 158
examples/js/lights/LightProbeGenerator.js

@@ -1,239 +1,231 @@
-THREE.LightProbeGenerator = {
+( function () {
 
+	var LightProbeGenerator = {
 	// https://www.ppsloan.org/publications/StupidSH36.pdf
-	fromCubeTexture: function ( cubeTexture ) {
+		fromCubeTexture: function ( cubeTexture ) {
 
-		var norm, lengthSq, weight, totalWeight = 0;
+			var norm,
+				lengthSq,
+				weight,
+				totalWeight = 0;
+			var coord = new THREE.Vector3();
+			var dir = new THREE.Vector3();
+			var color = new THREE.Color();
+			var shBasis = [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
+			var sh = new THREE.SphericalHarmonics3();
+			var shCoefficients = sh.coefficients;
 
-		var coord = new THREE.Vector3();
+			for ( var faceIndex = 0; faceIndex < 6; faceIndex ++ ) {
 
-		var dir = new THREE.Vector3();
+				var image = cubeTexture.image[ faceIndex ];
+				var width = image.width;
+				var height = image.height;
+				var canvas = document.createElement( 'canvas' );
+				canvas.width = width;
+				canvas.height = height;
+				var context = canvas.getContext( '2d' );
+				context.drawImage( image, 0, 0, width, height );
+				var imageData = context.getImageData( 0, 0, width, height );
+				var data = imageData.data;
+				var imageWidth = imageData.width; // assumed to be square
 
-		var color = new THREE.Color();
+				var pixelSize = 2 / imageWidth;
 
-		var shBasis = [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
+				for ( var i = 0, il = data.length; i < il; i += 4 ) {
 
-		var sh = new THREE.SphericalHarmonics3();
-		var shCoefficients = sh.coefficients;
+					// RGBA assumed
+					// pixel color
+					color.setRGB( data[ i ] / 255, data[ i + 1 ] / 255, data[ i + 2 ] / 255 ); // convert to linear color space
 
-		for ( var faceIndex = 0; faceIndex < 6; faceIndex ++ ) {
+					convertColorToLinear( color, cubeTexture.encoding ); // pixel coordinate on unit cube
 
-			var image = cubeTexture.image[ faceIndex ];
+					var pixelIndex = i / 4;
+					var col = - 1 + ( pixelIndex % imageWidth + 0.5 ) * pixelSize;
+					var row = 1 - ( Math.floor( pixelIndex / imageWidth ) + 0.5 ) * pixelSize;
 
-			var width = image.width;
-			var height = image.height;
+					switch ( faceIndex ) {
 
-			var canvas = document.createElement( 'canvas' );
+						case 0:
+							coord.set( - 1, row, - col );
+							break;
 
-			canvas.width = width;
-			canvas.height = height;
+						case 1:
+							coord.set( 1, row, col );
+							break;
 
-			var context = canvas.getContext( '2d' );
+						case 2:
+							coord.set( - col, 1, - row );
+							break;
 
-			context.drawImage( image, 0, 0, width, height );
+						case 3:
+							coord.set( - col, - 1, row );
+							break;
 
-			var imageData = context.getImageData( 0, 0, width, height );
+						case 4:
+							coord.set( - col, row, 1 );
+							break;
 
-			var data = imageData.data;
+						case 5:
+							coord.set( col, row, - 1 );
+							break;
 
-			var imageWidth = imageData.width; // assumed to be square
+					} // weight assigned to this pixel
 
-			var pixelSize = 2 / imageWidth;
 
-			for ( var i = 0, il = data.length; i < il; i += 4 ) { // RGBA assumed
+					lengthSq = coord.lengthSq();
+					weight = 4 / ( Math.sqrt( lengthSq ) * lengthSq );
+					totalWeight += weight; // direction vector to this pixel
 
-				// pixel color
-				color.setRGB( data[ i ] / 255, data[ i + 1 ] / 255, data[ i + 2 ] / 255 );
+					dir.copy( coord ).normalize(); // evaluate SH basis functions in direction dir
 
-				// convert to linear color space
-				convertColorToLinear( color, cubeTexture.encoding );
+					THREE.SphericalHarmonics3.getBasisAt( dir, shBasis ); // accummuulate
 
-				// pixel coordinate on unit cube
+					for ( var j = 0; j < 9; j ++ ) {
 
-				var pixelIndex = i / 4;
+						shCoefficients[ j ].x += shBasis[ j ] * color.r * weight;
+						shCoefficients[ j ].y += shBasis[ j ] * color.g * weight;
+						shCoefficients[ j ].z += shBasis[ j ] * color.b * weight;
 
-				var col = - 1 + ( pixelIndex % imageWidth + 0.5 ) * pixelSize;
-
-				var row = 1 - ( Math.floor( pixelIndex / imageWidth ) + 0.5 ) * pixelSize;
-
-				switch ( faceIndex ) {
-
-					case 0: coord.set( - 1, row, - col ); break;
-
-					case 1: coord.set( 1, row, col ); break;
-
-					case 2: coord.set( - col, 1, - row ); break;
-
-					case 3: coord.set( - col, - 1, row ); break;
-
-					case 4: coord.set( - col, row, 1 ); break;
-
-					case 5: coord.set( col, row, - 1 ); break;
+					}
 
 				}
 
-				// weight assigned to this pixel
-
-				lengthSq = coord.lengthSq();
+			} // normalize
 
-				weight = 4 / ( Math.sqrt( lengthSq ) * lengthSq );
 
-				totalWeight += weight;
+			norm = 4 * Math.PI / totalWeight;
 
-				// direction vector to this pixel
-				dir.copy( coord ).normalize();
+			for ( var j = 0; j < 9; j ++ ) {
 
-				// evaluate SH basis functions in direction dir
-				THREE.SphericalHarmonics3.getBasisAt( dir, shBasis );
-
-				// accummuulate
-				for ( var j = 0; j < 9; j ++ ) {
-
-					shCoefficients[ j ].x += shBasis[ j ] * color.r * weight;
-					shCoefficients[ j ].y += shBasis[ j ] * color.g * weight;
-					shCoefficients[ j ].z += shBasis[ j ] * color.b * weight;
-
-				}
+				shCoefficients[ j ].x *= norm;
+				shCoefficients[ j ].y *= norm;
+				shCoefficients[ j ].z *= norm;
 
 			}
 
-		}
+			return new THREE.LightProbe( sh );
 
-		// normalize
-		norm = ( 4 * Math.PI ) / totalWeight;
-
-		for ( var j = 0; j < 9; j ++ ) {
-
-			shCoefficients[ j ].x *= norm;
-			shCoefficients[ j ].y *= norm;
-			shCoefficients[ j ].z *= norm;
-
-		}
+		},
+		fromCubeRenderTarget: function ( renderer, cubeRenderTarget ) {
 
-		return new THREE.LightProbe( sh );
+			// The renderTarget must be set to RGBA in order to make readRenderTargetPixels works
+			var norm,
+				lengthSq,
+				weight,
+				totalWeight = 0;
+			var coord = new THREE.Vector3();
+			var dir = new THREE.Vector3();
+			var color = new THREE.Color();
+			var shBasis = [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
+			var sh = new THREE.SphericalHarmonics3();
+			var shCoefficients = sh.coefficients;
 
-	},
+			for ( var faceIndex = 0; faceIndex < 6; faceIndex ++ ) {
 
-	fromCubeRenderTarget: function ( renderer, cubeRenderTarget ) {
+				var imageWidth = cubeRenderTarget.width; // assumed to be square
 
-		// The renderTarget must be set to RGBA in order to make readRenderTargetPixels works
-		var norm, lengthSq, weight, totalWeight = 0;
+				var data = new Uint8Array( imageWidth * imageWidth * 4 );
+				renderer.readRenderTargetPixels( cubeRenderTarget, 0, 0, imageWidth, imageWidth, data, faceIndex );
+				var pixelSize = 2 / imageWidth;
 
-		var coord = new THREE.Vector3();
+				for ( var i = 0, il = data.length; i < il; i += 4 ) {
 
-		var dir = new THREE.Vector3();
+					// RGBA assumed
+					// pixel color
+					color.setRGB( data[ i ] / 255, data[ i + 1 ] / 255, data[ i + 2 ] / 255 ); // convert to linear color space
 
-		var color = new THREE.Color();
+					convertColorToLinear( color, cubeRenderTarget.texture.encoding ); // pixel coordinate on unit cube
 
-		var shBasis = [ 0, 0, 0, 0, 0, 0, 0, 0, 0 ];
+					var pixelIndex = i / 4;
+					var col = - 1 + ( pixelIndex % imageWidth + 0.5 ) * pixelSize;
+					var row = 1 - ( Math.floor( pixelIndex / imageWidth ) + 0.5 ) * pixelSize;
 
-		var sh = new THREE.SphericalHarmonics3();
-		var shCoefficients = sh.coefficients;
+					switch ( faceIndex ) {
 
-		for ( var faceIndex = 0; faceIndex < 6; faceIndex ++ ) {
+						case 0:
+							coord.set( 1, row, - col );
+							break;
 
-			var imageWidth = cubeRenderTarget.width; // assumed to be square
-			var data = new Uint8Array( imageWidth * imageWidth * 4 );
-			renderer.readRenderTargetPixels( cubeRenderTarget, 0, 0, imageWidth, imageWidth, data, faceIndex );
+						case 1:
+							coord.set( - 1, row, col );
+							break;
 
-			var pixelSize = 2 / imageWidth;
+						case 2:
+							coord.set( col, 1, - row );
+							break;
 
-			for ( var i = 0, il = data.length; i < il; i += 4 ) { // RGBA assumed
+						case 3:
+							coord.set( col, - 1, row );
+							break;
 
-				// pixel color
-				color.setRGB( data[ i ] / 255, data[ i + 1 ] / 255, data[ i + 2 ] / 255 );
+						case 4:
+							coord.set( col, row, 1 );
+							break;
 
-				// convert to linear color space
-				convertColorToLinear( color, cubeRenderTarget.texture.encoding );
+						case 5:
+							coord.set( - col, row, - 1 );
+							break;
 
-				// pixel coordinate on unit cube
+					} // weight assigned to this pixel
 
-				var pixelIndex = i / 4;
 
-				var col = - 1 + ( pixelIndex % imageWidth + 0.5 ) * pixelSize;
+					lengthSq = coord.lengthSq();
+					weight = 4 / ( Math.sqrt( lengthSq ) * lengthSq );
+					totalWeight += weight; // direction vector to this pixel
 
-				var row = 1 - ( Math.floor( pixelIndex / imageWidth ) + 0.5 ) * pixelSize;
+					dir.copy( coord ).normalize(); // evaluate SH basis functions in direction dir
 
-				switch ( faceIndex ) {
+					THREE.SphericalHarmonics3.getBasisAt( dir, shBasis ); // accummuulate
 
-					case 0: coord.set( 1, row, - col ); break;
+					for ( var j = 0; j < 9; j ++ ) {
 
-					case 1: coord.set( - 1, row, col ); break;
+						shCoefficients[ j ].x += shBasis[ j ] * color.r * weight;
+						shCoefficients[ j ].y += shBasis[ j ] * color.g * weight;
+						shCoefficients[ j ].z += shBasis[ j ] * color.b * weight;
 
-					case 2: coord.set( col, 1, - row ); break;
-
-					case 3: coord.set( col, - 1, row ); break;
-
-					case 4: coord.set( col, row, 1 ); break;
-
-					case 5: coord.set( - col, row, - 1 ); break;
+					}
 
 				}
 
-				// weight assigned to this pixel
-
-				lengthSq = coord.lengthSq();
-
-				weight = 4 / ( Math.sqrt( lengthSq ) * lengthSq );
+			} // normalize
 
-				totalWeight += weight;
 
-				// direction vector to this pixel
-				dir.copy( coord ).normalize();
+			norm = 4 * Math.PI / totalWeight;
 
-				// evaluate SH basis functions in direction dir
-				THREE.SphericalHarmonics3.getBasisAt( dir, shBasis );
+			for ( var j = 0; j < 9; j ++ ) {
 
-				// accummuulate
-				for ( var j = 0; j < 9; j ++ ) {
-
-					shCoefficients[ j ].x += shBasis[ j ] * color.r * weight;
-					shCoefficients[ j ].y += shBasis[ j ] * color.g * weight;
-					shCoefficients[ j ].z += shBasis[ j ] * color.b * weight;
-
-				}
+				shCoefficients[ j ].x *= norm;
+				shCoefficients[ j ].y *= norm;
+				shCoefficients[ j ].z *= norm;
 
 			}
 
-		}
-
-		// normalize
-		norm = ( 4 * Math.PI ) / totalWeight;
-
-		for ( var j = 0; j < 9; j ++ ) {
-
-			shCoefficients[ j ].x *= norm;
-			shCoefficients[ j ].y *= norm;
-			shCoefficients[ j ].z *= norm;
+			return new THREE.LightProbe( sh );
 
 		}
+	};
 
-		return new THREE.LightProbe( sh );
-
-	}
+	var convertColorToLinear = function ( color, encoding ) {
 
-};
+		switch ( encoding ) {
 
-var convertColorToLinear = function ( color, encoding ) {
+			case THREE.sRGBEncoding:
+				color.convertSRGBToLinear();
+				break;
 
-	switch ( encoding ) {
+			case THREE.LinearEncoding:
+				break;
 
-		case THREE.sRGBEncoding:
+			default:
+				console.warn( 'WARNING: LightProbeGenerator convertColorToLinear() encountered an unsupported encoding.' );
+				break;
 
-			color.convertSRGBToLinear();
-			break;
-
-		case THREE.LinearEncoding:
-
-			break;
-
-		default:
+		}
 
-			console.warn( 'WARNING: LightProbeGenerator convertColorToLinear() encountered an unsupported encoding.' );
-			break;
+		return color;
 
-	}
+	};
 
-	return color;
+	THREE.LightProbeGenerator = LightProbeGenerator;
 
-};
+} )();

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 11 - 16
examples/js/lights/RectAreaLightUniformsLib.js


+ 15 - 12
examples/js/lines/Line2.js

@@ -1,18 +1,21 @@
-THREE.Line2 = function ( geometry, material ) {
+( function () {
 
-	if ( geometry === undefined ) geometry = new THREE.LineGeometry();
-	if ( material === undefined ) material = new THREE.LineMaterial( { color: Math.random() * 0xffffff } );
+	var Line2 = function ( geometry, material ) {
 
-	THREE.LineSegments2.call( this, geometry, material );
+		if ( geometry === undefined ) geometry = new THREE.LineGeometry();
+		if ( material === undefined ) material = new THREE.LineMaterial( {
+			color: Math.random() * 0xffffff
+		} );
+		THREE.LineSegments2.call( this, geometry, material );
+		this.type = 'Line2';
 
-	this.type = 'Line2';
+	};
 
-};
+	Line2.prototype = Object.assign( Object.create( THREE.LineSegments2.prototype ), {
+		constructor: Line2,
+		isLine2: true
+	} );
 
-THREE.Line2.prototype = Object.assign( Object.create( THREE.LineSegments2.prototype ), {
+	THREE.Line2 = Line2;
 
-	constructor: THREE.Line2,
-
-	isLine2: true
-
-} );
+} )();

+ 54 - 64
examples/js/lines/LineGeometry.js

@@ -1,94 +1,84 @@
-THREE.LineGeometry = function () {
+( function () {
 
-	THREE.LineSegmentsGeometry.call( this );
+	var LineGeometry = function () {
 
-	this.type = 'LineGeometry';
+		THREE.LineSegmentsGeometry.call( this );
+		this.type = 'LineGeometry';
 
-};
+	};
 
-THREE.LineGeometry.prototype = Object.assign( Object.create( THREE.LineSegmentsGeometry.prototype ), {
+	LineGeometry.prototype = Object.assign( Object.create( THREE.LineSegmentsGeometry.prototype ), {
+		constructor: LineGeometry,
+		isLineGeometry: true,
+		setPositions: function ( array ) {
 
-	constructor: THREE.LineGeometry,
+			// converts [ x1, y1, z1,	x2, y2, z2, ... ] to pairs format
+			var length = array.length - 3;
+			var points = new Float32Array( 2 * length );
 
-	isLineGeometry: true,
+			for ( var i = 0; i < length; i += 3 ) {
 
-	setPositions: function ( array ) {
+				points[ 2 * i ] = array[ i ];
+				points[ 2 * i + 1 ] = array[ i + 1 ];
+				points[ 2 * i + 2 ] = array[ i + 2 ];
+				points[ 2 * i + 3 ] = array[ i + 3 ];
+				points[ 2 * i + 4 ] = array[ i + 4 ];
+				points[ 2 * i + 5 ] = array[ i + 5 ];
 
-		// converts [ x1, y1, z1,  x2, y2, z2, ... ] to pairs format
+			}
 
-		var length = array.length - 3;
-		var points = new Float32Array( 2 * length );
+			THREE.LineSegmentsGeometry.prototype.setPositions.call( this, points );
+			return this;
 
-		for ( var i = 0; i < length; i += 3 ) {
+		},
+		setColors: function ( array ) {
 
-			points[ 2 * i ] = array[ i ];
-			points[ 2 * i + 1 ] = array[ i + 1 ];
-			points[ 2 * i + 2 ] = array[ i + 2 ];
+			// converts [ r1, g1, b1,	r2, g2, b2, ... ] to pairs format
+			var length = array.length - 3;
+			var colors = new Float32Array( 2 * length );
 
-			points[ 2 * i + 3 ] = array[ i + 3 ];
-			points[ 2 * i + 4 ] = array[ i + 4 ];
-			points[ 2 * i + 5 ] = array[ i + 5 ];
+			for ( var i = 0; i < length; i += 3 ) {
 
-		}
-
-		THREE.LineSegmentsGeometry.prototype.setPositions.call( this, points );
-
-		return this;
-
-	},
-
-	setColors: function ( array ) {
-
-		// converts [ r1, g1, b1,  r2, g2, b2, ... ] to pairs format
-
-		var length = array.length - 3;
-		var colors = new Float32Array( 2 * length );
+				colors[ 2 * i ] = array[ i ];
+				colors[ 2 * i + 1 ] = array[ i + 1 ];
+				colors[ 2 * i + 2 ] = array[ i + 2 ];
+				colors[ 2 * i + 3 ] = array[ i + 3 ];
+				colors[ 2 * i + 4 ] = array[ i + 4 ];
+				colors[ 2 * i + 5 ] = array[ i + 5 ];
 
-		for ( var i = 0; i < length; i += 3 ) {
+			}
 
-			colors[ 2 * i ] = array[ i ];
-			colors[ 2 * i + 1 ] = array[ i + 1 ];
-			colors[ 2 * i + 2 ] = array[ i + 2 ];
+			THREE.LineSegmentsGeometry.prototype.setColors.call( this, colors );
+			return this;
 
-			colors[ 2 * i + 3 ] = array[ i + 3 ];
-			colors[ 2 * i + 4 ] = array[ i + 4 ];
-			colors[ 2 * i + 5 ] = array[ i + 5 ];
+		},
+		fromLine: function ( line ) {
 
-		}
+			var geometry = line.geometry;
 
-		THREE.LineSegmentsGeometry.prototype.setColors.call( this, colors );
+			if ( geometry.isGeometry ) {
 
-		return this;
+				console.error( 'THREE.LineGeometry no longer supports Geometry. Use THREE.BufferGeometry instead.' );
+				return;
 
-	},
+			} else if ( geometry.isBufferGeometry ) {
 
-	fromLine: function ( line ) {
+				this.setPositions( geometry.attributes.position.array ); // assumes non-indexed
 
-		var geometry = line.geometry;
+			} // set colors, maybe
 
-		if ( geometry.isGeometry ) {
 
-			console.error( 'THREE.LineGeometry no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
-			return;
+			return this;
 
-		} else if ( geometry.isBufferGeometry ) {
+		},
+		copy: function ( ) {
 
-			this.setPositions( geometry.attributes.position.array ); // assumes non-indexed
+			// todo
+			return this;
 
 		}
+	} );
 
-		// set colors, maybe
-
-		return this;
-
-	},
-
-	copy: function ( /* source */ ) {
-
-		// todo
-
-		return this;
-
-	}
+	THREE.LineGeometry = LineGeometry;
 
-} );
+} )();

+ 138 - 183
examples/js/lines/LineMaterial.js

@@ -1,38 +1,45 @@
-/**
+( function () {
+
+	/**
  * parameters = {
- *  color: <hex>,
- *  linewidth: <float>,
- *  dashed: <boolean>,
- *  dashScale: <float>,
- *  dashSize: <float>,
- *  dashOffset: <float>,
- *  gapSize: <float>,
- *  resolution: <Vector2>, // to be set by renderer
+ *	color: <hex>,
+ *	linewidth: <float>,
+ *	dashed: <boolean>,
+ *	dashScale: <float>,
+ *	dashSize: <float>,
+ *	dashOffset: <float>,
+ *	gapSize: <float>,
+ *	resolution: <Vector2>, // to be set by renderer
  * }
  */
 
-THREE.UniformsLib.line = {
-
-	linewidth: { value: 1 },
-	resolution: { value: new THREE.Vector2( 1, 1 ) },
-	dashScale: { value: 1 },
-	dashSize: { value: 1 },
-	dashOffset: { value: 0 },
-	gapSize: { value: 1 }, // todo FIX - maybe change to totalSize
-	opacity: { value: 1 }
-
-};
-
-THREE.ShaderLib[ 'line' ] = {
-
-	uniforms: THREE.UniformsUtils.merge( [
-		THREE.UniformsLib.common,
-		THREE.UniformsLib.fog,
-		THREE.UniformsLib.line
-	] ),
-
-	vertexShader:
-		`
+	THREE.UniformsLib.line = {
+		linewidth: {
+			value: 1
+		},
+		resolution: {
+			value: new THREE.Vector2( 1, 1 )
+		},
+		dashScale: {
+			value: 1
+		},
+		dashSize: {
+			value: 1
+		},
+		dashOffset: {
+			value: 0
+		},
+		gapSize: {
+			value: 1
+		},
+		// todo FIX - maybe change to totalSize
+		opacity: {
+			value: 1
+		}
+	};
+	THREE.ShaderLib[ 'line' ] = {
+		uniforms: THREE.UniformsUtils.merge( [ THREE.UniformsLib.common, THREE.UniformsLib.fog, THREE.UniformsLib.line ] ),
+		vertexShader: `
 		#include <common>
 		#include <color_pars_vertex>
 		#include <fog_pars_vertex>
@@ -177,9 +184,7 @@ THREE.ShaderLib[ 'line' ] = {
 
 		}
 		`,
-
-	fragmentShader:
-		`
+		fragmentShader: `
 		uniform vec3 diffuse;
 		uniform float opacity;
 
@@ -257,212 +262,162 @@ THREE.ShaderLib[ 'line' ] = {
 
 		}
 		`
-};
-
-THREE.LineMaterial = function ( parameters ) {
-
-	THREE.ShaderMaterial.call( this, {
-
-		type: 'LineMaterial',
-
-		uniforms: THREE.UniformsUtils.clone( THREE.ShaderLib[ 'line' ].uniforms ),
-
-		vertexShader: THREE.ShaderLib[ 'line' ].vertexShader,
-		fragmentShader: THREE.ShaderLib[ 'line' ].fragmentShader,
+	};
 
-		clipping: true // required for clipping support
+	var LineMaterial = function ( parameters ) {
 
-	} );
+		THREE.ShaderMaterial.call( this, {
+			type: 'LineMaterial',
+			uniforms: THREE.UniformsUtils.clone( THREE.ShaderLib[ 'line' ].uniforms ),
+			vertexShader: THREE.ShaderLib[ 'line' ].vertexShader,
+			fragmentShader: THREE.ShaderLib[ 'line' ].fragmentShader,
+			clipping: true // required for clipping support
 
-	this.dashed = false;
+		} );
+		this.dashed = false;
+		Object.defineProperties( this, {
+			color: {
+				enumerable: true,
+				get: function () {
 
-	Object.defineProperties( this, {
+					return this.uniforms.diffuse.value;
 
-		color: {
+				},
+				set: function ( value ) {
 
-			enumerable: true,
-
-			get: function () {
-
-				return this.uniforms.diffuse.value;
+					this.uniforms.diffuse.value = value;
 
+				}
 			},
+			linewidth: {
+				enumerable: true,
+				get: function () {
 
-			set: function ( value ) {
-
-				this.uniforms.diffuse.value = value;
-
-			}
-
-		},
-
-		linewidth: {
-
-			enumerable: true,
+					return this.uniforms.linewidth.value;
 
-			get: function () {
+				},
+				set: function ( value ) {
 
-				return this.uniforms.linewidth.value;
+					this.uniforms.linewidth.value = value;
 
+				}
 			},
+			dashScale: {
+				enumerable: true,
+				get: function () {
 
-			set: function ( value ) {
+					return this.uniforms.dashScale.value;
 
-				this.uniforms.linewidth.value = value;
+				},
+				set: function ( value ) {
 
-			}
-
-		},
-
-		dashScale: {
-
-			enumerable: true,
-
-			get: function () {
-
-				return this.uniforms.dashScale.value;
+					this.uniforms.dashScale.value = value;
 
+				}
 			},
+			dashSize: {
+				enumerable: true,
+				get: function () {
 
-			set: function ( value ) {
+					return this.uniforms.dashSize.value;
 
-				this.uniforms.dashScale.value = value;
-
-			}
-
-		},
-
-		dashSize: {
+				},
+				set: function ( value ) {
 
-			enumerable: true,
-
-			get: function () {
-
-				return this.uniforms.dashSize.value;
+					this.uniforms.dashSize.value = value;
 
+				}
 			},
+			dashOffset: {
+				enumerable: true,
+				get: function () {
 
-			set: function ( value ) {
-
-				this.uniforms.dashSize.value = value;
-
-			}
-
-		},
-
-		dashOffset: {
-
-			enumerable: true,
+					return this.uniforms.dashOffset.value;
 
-			get: function () {
+				},
+				set: function ( value ) {
 
-				return this.uniforms.dashOffset.value;
+					this.uniforms.dashOffset.value = value;
 
+				}
 			},
+			gapSize: {
+				enumerable: true,
+				get: function () {
 
-			set: function ( value ) {
+					return this.uniforms.gapSize.value;
 
-				this.uniforms.dashOffset.value = value;
+				},
+				set: function ( value ) {
 
-			}
-
-		},
-
-		gapSize: {
-
-			enumerable: true,
-
-			get: function () {
-
-				return this.uniforms.gapSize.value;
+					this.uniforms.gapSize.value = value;
 
+				}
 			},
+			opacity: {
+				enumerable: true,
+				get: function () {
 
-			set: function ( value ) {
-
-				this.uniforms.gapSize.value = value;
-
-			}
-
-		},
-
-		opacity: {
-
-			enumerable: true,
+					return this.uniforms.opacity.value;
 
-			get: function () {
+				},
+				set: function ( value ) {
 
-				return this.uniforms.opacity.value;
+					this.uniforms.opacity.value = value;
 
+				}
 			},
+			resolution: {
+				enumerable: true,
+				get: function () {
 
-			set: function ( value ) {
-
-				this.uniforms.opacity.value = value;
-
-			}
-
-		},
-
-		resolution: {
-
-			enumerable: true,
+					return this.uniforms.resolution.value;
 
-			get: function () {
+				},
+				set: function ( value ) {
 
-				return this.uniforms.resolution.value;
+					this.uniforms.resolution.value.copy( value );
 
+				}
 			},
+			alphaToCoverage: {
+				enumerable: true,
+				get: function () {
 
-			set: function ( value ) {
+					return Boolean( 'ALPHA_TO_COVERAGE' in this.defines );
 
-				this.uniforms.resolution.value.copy( value );
+				},
+				set: function ( value ) {
 
-			}
+					if ( Boolean( value ) !== Boolean( 'ALPHA_TO_COVERAGE' in this.defines ) ) {
 
-		},
+						this.needsUpdate = true;
 
-		alphaToCoverage: {
+					}
 
-			enumerable: true,
+					if ( value ) {
 
-			get: function () {
+						this.defines.ALPHA_TO_COVERAGE = '';
+						this.extensions.derivatives = true;
 
-				return Boolean( 'ALPHA_TO_COVERAGE' in this.defines );
+					} else {
 
-			},
-
-			set: function ( value ) {
+						delete this.defines.ALPHA_TO_COVERAGE;
+						this.extensions.derivatives = false;
 
-				if ( Boolean( value ) !== Boolean( 'ALPHA_TO_COVERAGE' in this.defines ) ) {
-
-					this.needsUpdate = true;
+					}
 
 				}
-
-				if ( value ) {
-
-					this.defines.ALPHA_TO_COVERAGE = '';
-					this.extensions.derivatives = true;
-
-				} else {
-
-					delete this.defines.ALPHA_TO_COVERAGE;
-					this.extensions.derivatives = false;
-
-				}
-
 			}
+		} );
+		this.setValues( parameters );
 
-		}
-
-	} );
-
-	this.setValues( parameters );
+	};
 
-};
+	LineMaterial.prototype = Object.create( THREE.ShaderMaterial.prototype );
+	LineMaterial.prototype.constructor = LineMaterial;
+	LineMaterial.prototype.isLineMaterial = true;
 
-THREE.LineMaterial.prototype = Object.create( THREE.ShaderMaterial.prototype );
-THREE.LineMaterial.prototype.constructor = THREE.LineMaterial;
+	THREE.LineMaterial = LineMaterial;
 
-THREE.LineMaterial.prototype.isLineMaterial = true;
+} )();

+ 169 - 206
examples/js/lines/LineSegments2.js

@@ -1,280 +1,243 @@
-THREE.LineSegments2 = function ( geometry, material ) {
+( function () {
 
-	if ( geometry === undefined ) geometry = new THREE.LineSegmentsGeometry();
-	if ( material === undefined ) material = new THREE.LineMaterial( { color: Math.random() * 0xffffff } );
+	var LineSegments2 = function ( geometry, material ) {
 
-	THREE.Mesh.call( this, geometry, material );
+		if ( geometry === undefined ) geometry = new THREE.LineSegmentsGeometry();
+		if ( material === undefined ) material = new THREE.LineMaterial( {
+			color: Math.random() * 0xffffff
+		} );
+		THREE.Mesh.call( this, geometry, material );
+		this.type = 'LineSegments2';
 
-	this.type = 'LineSegments2';
+	};
 
-};
+	LineSegments2.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), {
+		constructor: LineSegments2,
+		isLineSegments2: true,
+		computeLineDistances: function () {
 
-THREE.LineSegments2.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), {
+			// for backwards-compatability, but could be a method of THREE.LineSegmentsGeometry...
+			var start = new THREE.Vector3();
+			var end = new THREE.Vector3();
+			return function computeLineDistances() {
 
-	constructor: THREE.LineSegments2,
+				var geometry = this.geometry;
+				var instanceStart = geometry.attributes.instanceStart;
+				var instanceEnd = geometry.attributes.instanceEnd;
+				var lineDistances = new Float32Array( 2 * instanceStart.count );
 
-	isLineSegments2: true,
+				for ( var i = 0, j = 0, l = instanceStart.count; i < l; i ++, j += 2 ) {
 
-	computeLineDistances: ( function () { // for backwards-compatability, but could be a method of LineSegmentsGeometry...
+					start.fromBufferAttribute( instanceStart, i );
+					end.fromBufferAttribute( instanceEnd, i );
+					lineDistances[ j ] = j === 0 ? 0 : lineDistances[ j - 1 ];
+					lineDistances[ j + 1 ] = lineDistances[ j ] + start.distanceTo( end );
 
-		var start = new THREE.Vector3();
-		var end = new THREE.Vector3();
-
-		return function computeLineDistances() {
-
-			var geometry = this.geometry;
-
-			var instanceStart = geometry.attributes.instanceStart;
-			var instanceEnd = geometry.attributes.instanceEnd;
-			var lineDistances = new Float32Array( 2 * instanceStart.count );
-
-			for ( var i = 0, j = 0, l = instanceStart.count; i < l; i ++, j += 2 ) {
-
-				start.fromBufferAttribute( instanceStart, i );
-				end.fromBufferAttribute( instanceEnd, i );
-
-				lineDistances[ j ] = ( j === 0 ) ? 0 : lineDistances[ j - 1 ];
-				lineDistances[ j + 1 ] = lineDistances[ j ] + start.distanceTo( end );
-
-			}
-
-			var instanceDistanceBuffer = new THREE.InstancedInterleavedBuffer( lineDistances, 2, 1 ); // d0, d1
-
-			geometry.setAttribute( 'instanceDistanceStart', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 0 ) ); // d0
-			geometry.setAttribute( 'instanceDistanceEnd', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1
-
-			return this;
-
-		};
-
-	}() ),
-
-	raycast: ( function () {
-
-		var start = new THREE.Vector4();
-		var end = new THREE.Vector4();
-
-		var ssOrigin = new THREE.Vector4();
-		var ssOrigin3 = new THREE.Vector3();
-		var mvMatrix = new THREE.Matrix4();
-		var line = new THREE.Line3();
-		var closestPoint = new THREE.Vector3();
-
-		var box = new THREE.Box3();
-		var sphere = new THREE.Sphere();
-		var clipToWorldVector = new THREE.Vector4();
-
-		return function raycast( raycaster, intersects ) {
-
-			if ( raycaster.camera === null ) {
-
-				console.error( 'LineSegments2: "Raycaster.camera" needs to be set in order to raycast against LineSegments2.' );
-
-			}
-
-			var threshold = ( raycaster.params.Line2 !== undefined ) ? raycaster.params.Line2.threshold || 0 : 0;
-
-			var ray = raycaster.ray;
-			var camera = raycaster.camera;
-			var projectionMatrix = camera.projectionMatrix;
-
-			var matrixWorld = this.matrixWorld;
-			var geometry = this.geometry;
-			var material = this.material;
-			var resolution = material.resolution;
-			var lineWidth = material.linewidth + threshold;
+				}
 
-			var instanceStart = geometry.attributes.instanceStart;
-			var instanceEnd = geometry.attributes.instanceEnd;
+				var instanceDistanceBuffer = new THREE.InstancedInterleavedBuffer( lineDistances, 2, 1 ); // d0, d1
 
-			// camera forward is negative
-			var near = - camera.near;
+				geometry.setAttribute( 'instanceDistanceStart', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 0 ) ); // d0
 
-			// clip space is [ - 1, 1 ] so multiply by two to get the full
-			// width in clip space
-			var ssMaxWidth = 2.0 * Math.max( lineWidth / resolution.width, lineWidth / resolution.height );
+				geometry.setAttribute( 'instanceDistanceEnd', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1
 
-			//
+				return this;
 
-			// check if we intersect the sphere bounds
-			if ( geometry.boundingSphere === null ) {
+			};
 
-				geometry.computeBoundingSphere();
+		}(),
+		raycast: function () {
 
-			}
+			var start = new THREE.Vector4();
+			var end = new THREE.Vector4();
+			var ssOrigin = new THREE.Vector4();
+			var ssOrigin3 = new THREE.Vector3();
+			var mvMatrix = new THREE.Matrix4();
+			var line = new THREE.Line3();
+			var closestPoint = new THREE.Vector3();
+			var box = new THREE.Box3();
+			var sphere = new THREE.Sphere();
+			var clipToWorldVector = new THREE.Vector4();
+			return function raycast( raycaster, intersects ) {
 
-			sphere.copy( geometry.boundingSphere ).applyMatrix4( matrixWorld );
-			var distanceToSphere = Math.max( camera.near, sphere.distanceToPoint( ray.origin ) );
+				if ( raycaster.camera === null ) {
 
-			// get the w component to scale the world space line width
-			clipToWorldVector.set( 0, 0, - distanceToSphere, 1.0 ).applyMatrix4( camera.projectionMatrix );
-			clipToWorldVector.multiplyScalar( 1.0 / clipToWorldVector.w );
-			clipToWorldVector.applyMatrix4( camera.projectionMatrixInverse );
+					console.error( 'LineSegments2: "Raycaster.camera" needs to be set in order to raycast against LineSegments2.' );
 
-			// increase the sphere bounds by the worst case line screen space width
-			var sphereMargin = Math.abs( ssMaxWidth / clipToWorldVector.w ) * 0.5;
-			sphere.radius += sphereMargin;
+				}
 
-			if ( raycaster.ray.intersectsSphere( sphere ) === false ) {
+				var threshold = raycaster.params.Line2 !== undefined ? raycaster.params.Line2.threshold || 0 : 0;
+				var ray = raycaster.ray;
+				var camera = raycaster.camera;
+				var projectionMatrix = camera.projectionMatrix;
+				var matrixWorld = this.matrixWorld;
+				var geometry = this.geometry;
+				var material = this.material;
+				var resolution = material.resolution;
+				var lineWidth = material.linewidth + threshold;
+				var instanceStart = geometry.attributes.instanceStart;
+				var instanceEnd = geometry.attributes.instanceEnd; // camera forward is negative
 
-				return;
+				var near = - camera.near; // clip space is [ - 1, 1 ] so multiply by two to get the full
+				// width in clip space
 
-			}
+				var ssMaxWidth = 2.0 * Math.max( lineWidth / resolution.width, lineWidth / resolution.height ); //
+				// check if we intersect the sphere bounds
 
-			//
+				if ( geometry.boundingSphere === null ) {
 
-			// check if we intersect the box bounds
-			if ( geometry.boundingBox === null ) {
+					geometry.computeBoundingSphere();
 
-				geometry.computeBoundingBox();
+				}
 
-			}
+				sphere.copy( geometry.boundingSphere ).applyMatrix4( matrixWorld );
+				var distanceToSphere = Math.max( camera.near, sphere.distanceToPoint( ray.origin ) ); // get the w component to scale the world space line width
 
-			box.copy( geometry.boundingBox ).applyMatrix4( matrixWorld );
-			var distanceToBox = Math.max( camera.near, box.distanceToPoint( ray.origin ) );
+				clipToWorldVector.set( 0, 0, - distanceToSphere, 1.0 ).applyMatrix4( camera.projectionMatrix );
+				clipToWorldVector.multiplyScalar( 1.0 / clipToWorldVector.w );
+				clipToWorldVector.applyMatrix4( camera.projectionMatrixInverse ); // increase the sphere bounds by the worst case line screen space width
 
-			// get the w component to scale the world space line width
-			clipToWorldVector.set( 0, 0, - distanceToBox, 1.0 ).applyMatrix4( camera.projectionMatrix );
-			clipToWorldVector.multiplyScalar( 1.0 / clipToWorldVector.w );
-			clipToWorldVector.applyMatrix4( camera.projectionMatrixInverse );
+				var sphereMargin = Math.abs( ssMaxWidth / clipToWorldVector.w ) * 0.5;
+				sphere.radius += sphereMargin;
 
-			// increase the sphere bounds by the worst case line screen space width
-			var boxMargin = Math.abs( ssMaxWidth / clipToWorldVector.w ) * 0.5;
-			box.max.x += boxMargin;
-			box.max.y += boxMargin;
-			box.max.z += boxMargin;
-			box.min.x -= boxMargin;
-			box.min.y -= boxMargin;
-			box.min.z -= boxMargin;
+				if ( raycaster.ray.intersectsSphere( sphere ) === false ) {
 
-			if ( raycaster.ray.intersectsBox( box ) === false ) {
+					return;
 
-				return;
+				} //
+				// check if we intersect the box bounds
 
-			}
 
-			//
+				if ( geometry.boundingBox === null ) {
 
-			// pick a point 1 unit out along the ray to avoid the ray origin
-			// sitting at the camera origin which will cause "w" to be 0 when
-			// applying the projection matrix.
-			ray.at( 1, ssOrigin );
+					geometry.computeBoundingBox();
 
-			// ndc space [ - 1.0, 1.0 ]
-			ssOrigin.w = 1;
-			ssOrigin.applyMatrix4( camera.matrixWorldInverse );
-			ssOrigin.applyMatrix4( projectionMatrix );
-			ssOrigin.multiplyScalar( 1 / ssOrigin.w );
+				}
 
-			// screen space
-			ssOrigin.x *= resolution.x / 2;
-			ssOrigin.y *= resolution.y / 2;
-			ssOrigin.z = 0;
+				box.copy( geometry.boundingBox ).applyMatrix4( matrixWorld );
+				var distanceToBox = Math.max( camera.near, box.distanceToPoint( ray.origin ) ); // get the w component to scale the world space line width
 
-			ssOrigin3.copy( ssOrigin );
+				clipToWorldVector.set( 0, 0, - distanceToBox, 1.0 ).applyMatrix4( camera.projectionMatrix );
+				clipToWorldVector.multiplyScalar( 1.0 / clipToWorldVector.w );
+				clipToWorldVector.applyMatrix4( camera.projectionMatrixInverse ); // increase the sphere bounds by the worst case line screen space width
 
-			mvMatrix.multiplyMatrices( camera.matrixWorldInverse, matrixWorld );
+				var boxMargin = Math.abs( ssMaxWidth / clipToWorldVector.w ) * 0.5;
+				box.max.x += boxMargin;
+				box.max.y += boxMargin;
+				box.max.z += boxMargin;
+				box.min.x -= boxMargin;
+				box.min.y -= boxMargin;
+				box.min.z -= boxMargin;
 
-			for ( var i = 0, l = instanceStart.count; i < l; i ++ ) {
+				if ( raycaster.ray.intersectsBox( box ) === false ) {
 
-				start.fromBufferAttribute( instanceStart, i );
-				end.fromBufferAttribute( instanceEnd, i );
+					return;
 
-				start.w = 1;
-				end.w = 1;
+				} //
+				// pick a point 1 unit out along the ray to avoid the ray origin
+				// sitting at the camera origin which will cause "w" to be 0 when
+				// applying the projection matrix.
 
-				// camera space
-				start.applyMatrix4( mvMatrix );
-				end.applyMatrix4( mvMatrix );
 
-				// skip the segment if it's entirely behind the camera
-				var isBehindCameraNear = start.z > near && end.z > near;
-				if ( isBehindCameraNear ) {
+				ray.at( 1, ssOrigin ); // ndc space [ - 1.0, 1.0 ]
 
-					continue;
+				ssOrigin.w = 1;
+				ssOrigin.applyMatrix4( camera.matrixWorldInverse );
+				ssOrigin.applyMatrix4( projectionMatrix );
+				ssOrigin.multiplyScalar( 1 / ssOrigin.w ); // screen space
 
-				}
+				ssOrigin.x *= resolution.x / 2;
+				ssOrigin.y *= resolution.y / 2;
+				ssOrigin.z = 0;
+				ssOrigin3.copy( ssOrigin );
+				mvMatrix.multiplyMatrices( camera.matrixWorldInverse, matrixWorld );
 
-				// trim the segment if it extends behind camera near
-				if ( start.z > near ) {
+				for ( var i = 0, l = instanceStart.count; i < l; i ++ ) {
 
-					const deltaDist = start.z - end.z;
-					const t = ( start.z - near ) / deltaDist;
-					start.lerp( end, t );
+					start.fromBufferAttribute( instanceStart, i );
+					end.fromBufferAttribute( instanceEnd, i );
+					start.w = 1;
+					end.w = 1; // camera space
 
-				} else if ( end.z > near ) {
+					start.applyMatrix4( mvMatrix );
+					end.applyMatrix4( mvMatrix ); // skip the segment if it's entirely behind the camera
 
-					const deltaDist = end.z - start.z;
-					const t = ( end.z - near ) / deltaDist;
-					end.lerp( start, t );
+					var isBehindCameraNear = start.z > near && end.z > near;
 
-				}
+					if ( isBehindCameraNear ) {
 
-				// clip space
-				start.applyMatrix4( projectionMatrix );
-				end.applyMatrix4( projectionMatrix );
+						continue;
 
-				// ndc space [ - 1.0, 1.0 ]
-				start.multiplyScalar( 1 / start.w );
-				end.multiplyScalar( 1 / end.w );
+					} // trim the segment if it extends behind camera near
 
-				// screen space
-				start.x *= resolution.x / 2;
-				start.y *= resolution.y / 2;
 
-				end.x *= resolution.x / 2;
-				end.y *= resolution.y / 2;
+					if ( start.z > near ) {
 
-				// create 2d segment
-				line.start.copy( start );
-				line.start.z = 0;
+						const deltaDist = start.z - end.z;
+						const t = ( start.z - near ) / deltaDist;
+						start.lerp( end, t );
 
-				line.end.copy( end );
-				line.end.z = 0;
+					} else if ( end.z > near ) {
 
-				// get closest point on ray to segment
-				var param = line.closestPointToPointParameter( ssOrigin3, true );
-				line.at( param, closestPoint );
+						const deltaDist = end.z - start.z;
+						const t = ( end.z - near ) / deltaDist;
+						end.lerp( start, t );
 
-				// check if the intersection point is within clip space
-				var zPos = THREE.MathUtils.lerp( start.z, end.z, param );
-				var isInClipSpace = zPos >= - 1 && zPos <= 1;
+					} // clip space
 
-				var isInside = ssOrigin3.distanceTo( closestPoint ) < lineWidth * 0.5;
 
-				if ( isInClipSpace && isInside ) {
+					start.applyMatrix4( projectionMatrix );
+					end.applyMatrix4( projectionMatrix ); // ndc space [ - 1.0, 1.0 ]
 
-					line.start.fromBufferAttribute( instanceStart, i );
-					line.end.fromBufferAttribute( instanceEnd, i );
+					start.multiplyScalar( 1 / start.w );
+					end.multiplyScalar( 1 / end.w ); // screen space
 
-					line.start.applyMatrix4( matrixWorld );
-					line.end.applyMatrix4( matrixWorld );
+					start.x *= resolution.x / 2;
+					start.y *= resolution.y / 2;
+					end.x *= resolution.x / 2;
+					end.y *= resolution.y / 2; // create 2d segment
 
-					var pointOnLine = new THREE.Vector3();
-					var point = new THREE.Vector3();
+					line.start.copy( start );
+					line.start.z = 0;
+					line.end.copy( end );
+					line.end.z = 0; // get closest point on ray to segment
 
-					ray.distanceSqToSegment( line.start, line.end, point, pointOnLine );
+					var param = line.closestPointToPointParameter( ssOrigin3, true );
+					line.at( param, closestPoint ); // check if the intersection point is within clip space
 
-					intersects.push( {
+					var zPos = THREE.MathUtils.lerp( start.z, end.z, param );
+					var isInClipSpace = zPos >= - 1 && zPos <= 1;
+					var isInside = ssOrigin3.distanceTo( closestPoint ) < lineWidth * 0.5;
 
-						point: point,
-						pointOnLine: pointOnLine,
-						distance: ray.origin.distanceTo( point ),
+					if ( isInClipSpace && isInside ) {
 
-						object: this,
-						face: null,
-						faceIndex: i,
-						uv: null,
-						uv2: null,
+						line.start.fromBufferAttribute( instanceStart, i );
+						line.end.fromBufferAttribute( instanceEnd, i );
+						line.start.applyMatrix4( matrixWorld );
+						line.end.applyMatrix4( matrixWorld );
+						var pointOnLine = new THREE.Vector3();
+						var point = new THREE.Vector3();
+						ray.distanceSqToSegment( line.start, line.end, point, pointOnLine );
+						intersects.push( {
+							point: point,
+							pointOnLine: pointOnLine,
+							distance: ray.origin.distanceTo( point ),
+							object: this,
+							face: null,
+							faceIndex: i,
+							uv: null,
+							uv2: null
+						} );
 
-					} );
+					}
 
 				}
 
-			}
+			};
 
-		};
+		}()
+	} );
 
-	}() )
+	THREE.LineSegments2 = LineSegments2;
 
-} );
+} )();

+ 130 - 159
examples/js/lines/LineSegmentsGeometry.js

@@ -1,248 +1,219 @@
-THREE.LineSegmentsGeometry = function () {
+( function () {
 
-	THREE.InstancedBufferGeometry.call( this );
+	var LineSegmentsGeometry = function () {
 
-	this.type = 'LineSegmentsGeometry';
+		THREE.InstancedBufferGeometry.call( this );
+		this.type = 'LineSegmentsGeometry';
+		var positions = [ - 1, 2, 0, 1, 2, 0, - 1, 1, 0, 1, 1, 0, - 1, 0, 0, 1, 0, 0, - 1, - 1, 0, 1, - 1, 0 ];
+		var uvs = [ - 1, 2, 1, 2, - 1, 1, 1, 1, - 1, - 1, 1, - 1, - 1, - 2, 1, - 2 ];
+		var index = [ 0, 2, 1, 2, 3, 1, 2, 4, 3, 4, 5, 3, 4, 6, 5, 6, 7, 5 ];
+		this.setIndex( index );
+		this.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
+		this.setAttribute( 'uv', new THREE.Float32BufferAttribute( uvs, 2 ) );
 
-	var positions = [ - 1, 2, 0, 1, 2, 0, - 1, 1, 0, 1, 1, 0, - 1, 0, 0, 1, 0, 0, - 1, - 1, 0, 1, - 1, 0 ];
-	var uvs = [ - 1, 2, 1, 2, - 1, 1, 1, 1, - 1, - 1, 1, - 1, - 1, - 2, 1, - 2 ];
-	var index = [ 0, 2, 1, 2, 3, 1, 2, 4, 3, 4, 5, 3, 4, 6, 5, 6, 7, 5 ];
+	};
 
-	this.setIndex( index );
-	this.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
-	this.setAttribute( 'uv', new THREE.Float32BufferAttribute( uvs, 2 ) );
+	LineSegmentsGeometry.prototype = Object.assign( Object.create( THREE.InstancedBufferGeometry.prototype ), {
+		constructor: LineSegmentsGeometry,
+		isLineSegmentsGeometry: true,
+		applyMatrix4: function ( matrix ) {
 
-};
-
-THREE.LineSegmentsGeometry.prototype = Object.assign( Object.create( THREE.InstancedBufferGeometry.prototype ), {
-
-	constructor: THREE.LineSegmentsGeometry,
-
-	isLineSegmentsGeometry: true,
-
-	applyMatrix4: function ( matrix ) {
-
-		var start = this.attributes.instanceStart;
-		var end = this.attributes.instanceEnd;
-
-		if ( start !== undefined ) {
-
-			start.applyMatrix4( matrix );
-
-			end.applyMatrix4( matrix );
-
-			start.needsUpdate = true;
-
-		}
-
-		if ( this.boundingBox !== null ) {
-
-			this.computeBoundingBox();
-
-		}
-
-		if ( this.boundingSphere !== null ) {
-
-			this.computeBoundingSphere();
-
-		}
-
-		return this;
-
-	},
-
-	setPositions: function ( array ) {
-
-		var lineSegments;
-
-		if ( array instanceof Float32Array ) {
-
-			lineSegments = array;
-
-		} else if ( Array.isArray( array ) ) {
-
-			lineSegments = new Float32Array( array );
+			var start = this.attributes.instanceStart;
+			var end = this.attributes.instanceEnd;
 
-		}
+			if ( start !== undefined ) {
 
-		var instanceBuffer = new THREE.InstancedInterleavedBuffer( lineSegments, 6, 1 ); // xyz, xyz
+				start.applyMatrix4( matrix );
+				end.applyMatrix4( matrix );
+				start.needsUpdate = true;
 
-		this.setAttribute( 'instanceStart', new THREE.InterleavedBufferAttribute( instanceBuffer, 3, 0 ) ); // xyz
-		this.setAttribute( 'instanceEnd', new THREE.InterleavedBufferAttribute( instanceBuffer, 3, 3 ) ); // xyz
+			}
 
-		//
+			if ( this.boundingBox !== null ) {
 
-		this.computeBoundingBox();
-		this.computeBoundingSphere();
+				this.computeBoundingBox();
 
-		return this;
+			}
 
-	},
+			if ( this.boundingSphere !== null ) {
 
-	setColors: function ( array ) {
+				this.computeBoundingSphere();
 
-		var colors;
+			}
 
-		if ( array instanceof Float32Array ) {
+			return this;
 
-			colors = array;
+		},
+		setPositions: function ( array ) {
 
-		} else if ( Array.isArray( array ) ) {
+			var lineSegments;
 
-			colors = new Float32Array( array );
+			if ( array instanceof Float32Array ) {
 
-		}
+				lineSegments = array;
 
-		var instanceColorBuffer = new THREE.InstancedInterleavedBuffer( colors, 6, 1 ); // rgb, rgb
+			} else if ( Array.isArray( array ) ) {
 
-		this.setAttribute( 'instanceColorStart', new THREE.InterleavedBufferAttribute( instanceColorBuffer, 3, 0 ) ); // rgb
-		this.setAttribute( 'instanceColorEnd', new THREE.InterleavedBufferAttribute( instanceColorBuffer, 3, 3 ) ); // rgb
+				lineSegments = new Float32Array( array );
 
-		return this;
+			}
 
-	},
+			var instanceBuffer = new THREE.InstancedInterleavedBuffer( lineSegments, 6, 1 ); // xyz, xyz
 
-	fromWireframeGeometry: function ( geometry ) {
+			this.setAttribute( 'instanceStart', new THREE.InterleavedBufferAttribute( instanceBuffer, 3, 0 ) ); // xyz
 
-		this.setPositions( geometry.attributes.position.array );
+			this.setAttribute( 'instanceEnd', new THREE.InterleavedBufferAttribute( instanceBuffer, 3, 3 ) ); // xyz
+			//
 
-		return this;
+			this.computeBoundingBox();
+			this.computeBoundingSphere();
+			return this;
 
-	},
+		},
+		setColors: function ( array ) {
 
-	fromEdgesGeometry: function ( geometry ) {
+			var colors;
 
-		this.setPositions( geometry.attributes.position.array );
+			if ( array instanceof Float32Array ) {
 
-		return this;
+				colors = array;
 
-	},
+			} else if ( Array.isArray( array ) ) {
 
-	fromMesh: function ( mesh ) {
+				colors = new Float32Array( array );
 
-		this.fromWireframeGeometry( new THREE.WireframeGeometry( mesh.geometry ) );
+			}
 
-		// set colors, maybe
+			var instanceColorBuffer = new THREE.InstancedInterleavedBuffer( colors, 6, 1 ); // rgb, rgb
 
-		return this;
+			this.setAttribute( 'instanceColorStart', new THREE.InterleavedBufferAttribute( instanceColorBuffer, 3, 0 ) ); // rgb
 
-	},
+			this.setAttribute( 'instanceColorEnd', new THREE.InterleavedBufferAttribute( instanceColorBuffer, 3, 3 ) ); // rgb
 
-	fromLineSegments: function ( lineSegments ) {
+			return this;
 
-		var geometry = lineSegments.geometry;
+		},
+		fromWireframeGeometry: function ( geometry ) {
 
-		if ( geometry.isGeometry ) {
+			this.setPositions( geometry.attributes.position.array );
+			return this;
 
-			console.error( 'THREE.LineSegmentsGeometry no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
-			return;
+		},
+		fromEdgesGeometry: function ( geometry ) {
 
-		} else if ( geometry.isBufferGeometry ) {
+			this.setPositions( geometry.attributes.position.array );
+			return this;
 
-			this.setPositions( geometry.attributes.position.array ); // assumes non-indexed
+		},
+		fromMesh: function ( mesh ) {
 
-		}
+			this.fromWireframeGeometry( new THREE.WireframeGeometry( mesh.geometry ) ); // set colors, maybe
 
-		// set colors, maybe
+			return this;
 
-		return this;
+		},
+		fromLineSegments: function ( lineSegments ) {
 
-	},
+			var geometry = lineSegments.geometry;
 
-	computeBoundingBox: function () {
+			if ( geometry.isGeometry ) {
 
-		var box = new THREE.Box3();
+				console.error( 'THREE.LineSegmentsGeometry no longer supports Geometry. Use THREE.BufferGeometry instead.' );
+				return;
 
-		return function computeBoundingBox() {
+			} else if ( geometry.isBufferGeometry ) {
 
-			if ( this.boundingBox === null ) {
+				this.setPositions( geometry.attributes.position.array ); // assumes non-indexed
 
-				this.boundingBox = new THREE.Box3();
+			} // set colors, maybe
 
-			}
 
-			var start = this.attributes.instanceStart;
-			var end = this.attributes.instanceEnd;
+			return this;
 
-			if ( start !== undefined && end !== undefined ) {
+		},
+		computeBoundingBox: function () {
 
-				this.boundingBox.setFromBufferAttribute( start );
+			var box = new THREE.Box3();
+			return function computeBoundingBox() {
 
-				box.setFromBufferAttribute( end );
+				if ( this.boundingBox === null ) {
 
-				this.boundingBox.union( box );
+					this.boundingBox = new THREE.Box3();
 
-			}
+				}
 
-		};
+				var start = this.attributes.instanceStart;
+				var end = this.attributes.instanceEnd;
 
-	}(),
+				if ( start !== undefined && end !== undefined ) {
 
-	computeBoundingSphere: function () {
+					this.boundingBox.setFromBufferAttribute( start );
+					box.setFromBufferAttribute( end );
+					this.boundingBox.union( box );
 
-		var vector = new THREE.Vector3();
+				}
 
-		return function computeBoundingSphere() {
+			};
 
-			if ( this.boundingSphere === null ) {
+		}(),
+		computeBoundingSphere: function () {
 
-				this.boundingSphere = new THREE.Sphere();
+			var vector = new THREE.Vector3();
+			return function computeBoundingSphere() {
 
-			}
+				if ( this.boundingSphere === null ) {
 
-			if ( this.boundingBox === null ) {
+					this.boundingSphere = new THREE.Sphere();
 
-				this.computeBoundingBox();
+				}
 
-			}
+				if ( this.boundingBox === null ) {
 
-			var start = this.attributes.instanceStart;
-			var end = this.attributes.instanceEnd;
+					this.computeBoundingBox();
 
-			if ( start !== undefined && end !== undefined ) {
+				}
 
-				var center = this.boundingSphere.center;
+				var start = this.attributes.instanceStart;
+				var end = this.attributes.instanceEnd;
 
-				this.boundingBox.getCenter( center );
+				if ( start !== undefined && end !== undefined ) {
 
-				var maxRadiusSq = 0;
+					var center = this.boundingSphere.center;
+					this.boundingBox.getCenter( center );
+					var maxRadiusSq = 0;
 
-				for ( var i = 0, il = start.count; i < il; i ++ ) {
+					for ( var i = 0, il = start.count; i < il; i ++ ) {
 
-					vector.fromBufferAttribute( start, i );
-					maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
+						vector.fromBufferAttribute( start, i );
+						maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
+						vector.fromBufferAttribute( end, i );
+						maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
 
-					vector.fromBufferAttribute( end, i );
-					maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
+					}
 
-				}
+					this.boundingSphere.radius = Math.sqrt( maxRadiusSq );
 
-				this.boundingSphere.radius = Math.sqrt( maxRadiusSq );
+					if ( isNaN( this.boundingSphere.radius ) ) {
 
-				if ( isNaN( this.boundingSphere.radius ) ) {
+						console.error( 'THREE.LineSegmentsGeometry.computeBoundingSphere(): Computed radius is NaN. The instanced position data is likely to have NaN values.', this );
 
-					console.error( 'THREE.LineSegmentsGeometry.computeBoundingSphere(): Computed radius is NaN. The instanced position data is likely to have NaN values.', this );
+					}
 
 				}
 
-			}
-
-		};
-
-	}(),
-
-	toJSON: function () {
+			};
 
-		// todo
+		}(),
+		toJSON: function () { // todo
+		},
+		applyMatrix: function ( matrix ) {
 
-	},
+			console.warn( 'THREE.LineSegmentsGeometry: applyMatrix() has been renamed to applyMatrix4().' );
+			return this.applyMatrix4( matrix );
 
-	applyMatrix: function ( matrix ) {
-
-		console.warn( 'THREE.LineSegmentsGeometry: applyMatrix() has been renamed to applyMatrix4().' );
-
-		return this.applyMatrix4( matrix );
+		}
+	} );
 
-	}
+	THREE.LineSegmentsGeometry = LineSegmentsGeometry;
 
-} );
+} )();

+ 36 - 35
examples/js/lines/Wireframe.js

@@ -1,52 +1,53 @@
-THREE.Wireframe = function ( geometry, material ) {
+( function () {
 
-	THREE.Mesh.call( this );
+	var Wireframe = function ( geometry, material ) {
 
-	this.type = 'Wireframe';
+		THREE.Mesh.call( this );
+		this.type = 'Wireframe';
+		this.geometry = geometry !== undefined ? geometry : new THREE.LineSegmentsGeometry();
+		this.material = material !== undefined ? material : new THREE.LineMaterial( {
+			color: Math.random() * 0xffffff
+		} );
 
-	this.geometry = geometry !== undefined ? geometry : new THREE.LineSegmentsGeometry();
-	this.material = material !== undefined ? material : new THREE.LineMaterial( { color: Math.random() * 0xffffff } );
+	};
 
-};
+	Wireframe.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), {
+		constructor: Wireframe,
+		isWireframe: true,
+		computeLineDistances: function () {
 
-THREE.Wireframe.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), {
+			// for backwards-compatability, but could be a method of THREE.LineSegmentsGeometry...
+			var start = new THREE.Vector3();
+			var end = new THREE.Vector3();
+			return function computeLineDistances() {
 
-	constructor: THREE.Wireframe,
+				var geometry = this.geometry;
+				var instanceStart = geometry.attributes.instanceStart;
+				var instanceEnd = geometry.attributes.instanceEnd;
+				var lineDistances = new Float32Array( 2 * instanceStart.count );
 
-	isWireframe: true,
+				for ( var i = 0, j = 0, l = instanceStart.count; i < l; i ++, j += 2 ) {
 
-	computeLineDistances: ( function () { // for backwards-compatability, but could be a method of LineSegmentsGeometry...
+					start.fromBufferAttribute( instanceStart, i );
+					end.fromBufferAttribute( instanceEnd, i );
+					lineDistances[ j ] = j === 0 ? 0 : lineDistances[ j - 1 ];
+					lineDistances[ j + 1 ] = lineDistances[ j ] + start.distanceTo( end );
 
-		var start = new THREE.Vector3();
-		var end = new THREE.Vector3();
+				}
 
-		return function computeLineDistances() {
+				var instanceDistanceBuffer = new THREE.InstancedInterleavedBuffer( lineDistances, 2, 1 ); // d0, d1
 
-			var geometry = this.geometry;
+				geometry.setAttribute( 'instanceDistanceStart', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 0 ) ); // d0
 
-			var instanceStart = geometry.attributes.instanceStart;
-			var instanceEnd = geometry.attributes.instanceEnd;
-			var lineDistances = new Float32Array( 2 * instanceStart.count );
+				geometry.setAttribute( 'instanceDistanceEnd', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1
 
-			for ( var i = 0, j = 0, l = instanceStart.count; i < l; i ++, j += 2 ) {
+				return this;
 
-				start.fromBufferAttribute( instanceStart, i );
-				end.fromBufferAttribute( instanceEnd, i );
+			};
 
-				lineDistances[ j ] = ( j === 0 ) ? 0 : lineDistances[ j - 1 ];
-				lineDistances[ j + 1 ] = lineDistances[ j ] + start.distanceTo( end );
+		}()
+	} );
 
-			}
+	THREE.Wireframe = Wireframe;
 
-			var instanceDistanceBuffer = new THREE.InstancedInterleavedBuffer( lineDistances, 2, 1 ); // d0, d1
-
-			geometry.setAttribute( 'instanceDistanceStart', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 0 ) ); // d0
-			geometry.setAttribute( 'instanceDistanceEnd', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1
-
-			return this;
-
-		};
-
-	}() )
-
-} );
+} )();

+ 12 - 13
examples/js/lines/WireframeGeometry2.js

@@ -1,19 +1,18 @@
-THREE.WireframeGeometry2 = function ( geometry ) {
+( function () {
 
-	THREE.LineSegmentsGeometry.call( this );
+	var WireframeGeometry2 = function ( geometry ) {
 
-	this.type = 'WireframeGeometry2';
+		THREE.LineSegmentsGeometry.call( this );
+		this.type = 'WireframeGeometry2';
+		this.fromWireframeGeometry( new THREE.WireframeGeometry( geometry ) ); // set colors, maybe
 
-	this.fromWireframeGeometry( new THREE.WireframeGeometry( geometry ) );
+	};
 
-	// set colors, maybe
+	WireframeGeometry2.prototype = Object.assign( Object.create( THREE.LineSegmentsGeometry.prototype ), {
+		constructor: WireframeGeometry2,
+		isWireframeGeometry2: true
+	} );
 
-};
+	THREE.WireframeGeometry2 = WireframeGeometry2;
 
-THREE.WireframeGeometry2.prototype = Object.assign( Object.create( THREE.LineSegmentsGeometry.prototype ), {
-
-	constructor: THREE.WireframeGeometry2,
-
-	isWireframeGeometry2: true
-
-} );
+} )();

+ 1264 - 0
examples/js/loaders/3DMLoader.js

@@ -0,0 +1,1264 @@
+( function () {
+
+	var Rhino3dmLoader = function ( manager ) {
+
+		THREE.Loader.call( this, manager );
+		this.libraryPath = '';
+		this.libraryPending = null;
+		this.libraryBinary = null;
+		this.libraryConfig = {};
+		this.url = '';
+		this.workerLimit = 4;
+		this.workerPool = [];
+		this.workerNextTaskID = 1;
+		this.workerSourceURL = '';
+		this.workerConfig = {};
+		this.materials = [];
+
+	};
+
+	Rhino3dmLoader.taskCache = new WeakMap();
+	Rhino3dmLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+		constructor: Rhino3dmLoader,
+		setLibraryPath: function ( path ) {
+
+			this.libraryPath = path;
+			return this;
+
+		},
+		setWorkerLimit: function ( workerLimit ) {
+
+			this.workerLimit = workerLimit;
+			return this;
+
+		},
+		load: function ( url, onLoad, onProgress, onError ) {
+
+			var loader = new THREE.FileLoader( this.manager );
+			loader.setPath( this.path );
+			loader.setResponseType( 'arraybuffer' );
+			loader.setRequestHeader( this.requestHeader );
+			this.url = url;
+			loader.load( url, buffer => {
+
+				// Check for an existing task using this buffer. A transferred buffer cannot be transferred
+				// again from this thread.
+				if ( Rhino3dmLoader.taskCache.has( buffer ) ) {
+
+					var cachedTask = Rhino3dmLoader.taskCache.get( buffer );
+					return cachedTask.promise.then( onLoad ).catch( onError );
+
+				}
+
+				this.decodeObjects( buffer, url ).then( onLoad ).catch( onError );
+
+			}, onProgress, onError );
+
+		},
+		debug: function () {
+
+			console.log( 'Task load: ', this.workerPool.map( worker => worker._taskLoad ) );
+
+		},
+		decodeObjects: function ( buffer, url ) {
+
+			var worker;
+			var taskID;
+			var taskCost = buffer.byteLength;
+
+			var objectPending = this._getWorker( taskCost ).then( _worker => {
+
+				worker = _worker;
+				taskID = this.workerNextTaskID ++; //hmmm
+
+				return new Promise( ( resolve, reject ) => {
+
+					worker._callbacks[ taskID ] = {
+						resolve,
+						reject
+					};
+					worker.postMessage( {
+						type: 'decode',
+						id: taskID,
+						buffer
+					}, [ buffer ] ); //this.debug();
+
+				} );
+
+			} ).then( message => this._createGeometry( message.data ) ); // Remove task from the task list.
+			// Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416)
+
+
+			objectPending.catch( () => true ).then( () => {
+
+				if ( worker && taskID ) {
+
+					this._releaseTask( worker, taskID ); //this.debug();
+
+				}
+
+			} ); // Cache the task result.
+
+			Rhino3dmLoader.taskCache.set( buffer, {
+				url: url,
+				promise: objectPending
+			} );
+			return objectPending;
+
+		},
+		parse: function ( data, onLoad, onError ) {
+
+			this.decodeObjects( data, '' ).then( onLoad ).catch( onError );
+
+		},
+		_compareMaterials: function ( material ) {
+
+			var mat = {};
+			mat.name = material.name;
+			mat.color = {};
+			mat.color.r = material.color.r;
+			mat.color.g = material.color.g;
+			mat.color.b = material.color.b;
+			mat.type = material.type;
+
+			for ( var i = 0; i < this.materials.length; i ++ ) {
+
+				var m = this.materials[ i ];
+				var _mat = {};
+				_mat.name = m.name;
+				_mat.color = {};
+				_mat.color.r = m.color.r;
+				_mat.color.g = m.color.g;
+				_mat.color.b = m.color.b;
+				_mat.type = m.type;
+
+				if ( JSON.stringify( mat ) === JSON.stringify( _mat ) ) {
+
+					return m;
+
+				}
+
+			}
+
+			this.materials.push( material );
+			return material;
+
+		},
+		_createMaterial: function ( material ) {
+
+			if ( material === undefined ) {
+
+				return new THREE.MeshStandardMaterial( {
+					color: new THREE.Color( 1, 1, 1 ),
+					metalness: 0.8,
+					name: 'default',
+					side: 2
+				} );
+
+			}
+
+			var _diffuseColor = material.diffuseColor;
+			var diffusecolor = new THREE.Color( _diffuseColor.r / 255.0, _diffuseColor.g / 255.0, _diffuseColor.b / 255.0 );
+
+			if ( _diffuseColor.r === 0 && _diffuseColor.g === 0 && _diffuseColor.b === 0 ) {
+
+				diffusecolor.r = 1;
+				diffusecolor.g = 1;
+				diffusecolor.b = 1;
+
+			} // console.log( material );
+
+
+			var mat = new THREE.MeshStandardMaterial( {
+				color: diffusecolor,
+				name: material.name,
+				side: 2,
+				transparent: material.transparency > 0 ? true : false,
+				opacity: 1.0 - material.transparency
+			} );
+			var textureLoader = new THREE.TextureLoader();
+
+			for ( var i = 0; i < material.textures.length; i ++ ) {
+
+				var texture = material.textures[ i ];
+
+				if ( texture.image !== null ) {
+
+					var map = textureLoader.load( texture.image );
+
+					switch ( texture.type ) {
+
+						case 'Diffuse':
+							mat.map = map;
+							break;
+
+						case 'Bump':
+							mat.bumpMap = map;
+							break;
+
+						case 'Transparency':
+							mat.alphaMap = map;
+							mat.transparent = true;
+							break;
+
+						case 'Emap':
+							mat.envMap = map;
+							break;
+
+					}
+
+				}
+
+			}
+
+			return mat;
+
+		},
+		_createGeometry: function ( data ) {
+
+			// console.log(data);
+			var object = new THREE.Object3D();
+			var instanceDefinitionObjects = [];
+			var instanceDefinitions = [];
+			var instanceReferences = [];
+			object.userData[ 'layers' ] = data.layers;
+			object.userData[ 'groups' ] = data.groups;
+			object.userData[ 'settings' ] = data.settings;
+			object.userData[ 'objectType' ] = 'File3dm';
+			object.userData[ 'materials' ] = null;
+			object.name = this.url;
+			var objects = data.objects;
+			var materials = data.materials;
+
+			for ( var i = 0; i < objects.length; i ++ ) {
+
+				var obj = objects[ i ];
+				var attributes = obj.attributes;
+
+				switch ( obj.objectType ) {
+
+					case 'InstanceDefinition':
+						instanceDefinitions.push( obj );
+						break;
+
+					case 'InstanceReference':
+						instanceReferences.push( obj );
+						break;
+
+					default:
+						if ( attributes.materialIndex >= 0 ) {
+
+							var rMaterial = materials[ attributes.materialIndex ];
+
+							var material = this._createMaterial( rMaterial );
+
+							material = this._compareMaterials( material );
+
+							var _object = this._createObject( obj, material );
+
+						} else {
+
+							var material = this._createMaterial();
+
+							var _object = this._createObject( obj, material );
+
+						}
+
+						if ( _object === undefined ) {
+
+							continue;
+
+						}
+
+						var layer = data.layers[ attributes.layerIndex ];
+						_object.visible = layer ? data.layers[ attributes.layerIndex ].visible : true;
+
+						if ( attributes.isInstanceDefinitionObject ) {
+
+							instanceDefinitionObjects.push( _object );
+
+						} else {
+
+							object.add( _object );
+
+						}
+
+						break;
+
+				}
+
+			}
+
+			for ( var i = 0; i < instanceDefinitions.length; i ++ ) {
+
+				var iDef = instanceDefinitions[ i ];
+				var objects = [];
+
+				for ( var j = 0; j < iDef.attributes.objectIds.length; j ++ ) {
+
+					var objId = iDef.attributes.objectIds[ j ];
+
+					for ( var p = 0; p < instanceDefinitionObjects.length; p ++ ) {
+
+						var idoId = instanceDefinitionObjects[ p ].userData.attributes.id;
+
+						if ( objId === idoId ) {
+
+							objects.push( instanceDefinitionObjects[ p ] );
+
+						}
+
+					}
+
+				} // Currently clones geometry and does not take advantage of instancing
+
+
+				for ( var j = 0; j < instanceReferences.length; j ++ ) {
+
+					var iRef = instanceReferences[ j ];
+
+					if ( iRef.geometry.parentIdefId === iDef.attributes.id ) {
+
+						var iRefObject = new THREE.Object3D();
+						var xf = iRef.geometry.xform.array;
+						var matrix = new THREE.Matrix4();
+						matrix.set( xf[ 0 ], xf[ 1 ], xf[ 2 ], xf[ 3 ], xf[ 4 ], xf[ 5 ], xf[ 6 ], xf[ 7 ], xf[ 8 ], xf[ 9 ], xf[ 10 ], xf[ 11 ], xf[ 12 ], xf[ 13 ], xf[ 14 ], xf[ 15 ] );
+						iRefObject.applyMatrix4( matrix );
+
+						for ( var p = 0; p < objects.length; p ++ ) {
+
+							iRefObject.add( objects[ p ].clone( true ) );
+
+						}
+
+						object.add( iRefObject );
+
+					}
+
+				}
+
+			}
+
+			object.userData[ 'materials' ] = this.materials;
+			return object;
+
+		},
+		_createObject: function ( obj, mat ) {
+
+			var loader = new THREE.BufferGeometryLoader();
+			var attributes = obj.attributes;
+
+			switch ( obj.objectType ) {
+
+				case 'Point':
+				case 'PointSet':
+					var geometry = loader.parse( obj.geometry );
+					var material = null;
+
+					if ( geometry.attributes.hasOwnProperty( 'color' ) ) {
+
+						material = new THREE.PointsMaterial( {
+							vertexColors: true,
+							sizeAttenuation: false,
+							size: 2
+						} );
+
+					} else {
+
+						var _color = attributes.drawColor;
+						var color = new THREE.Color( _color.r / 255.0, _color.g / 255.0, _color.b / 255.0 );
+						material = new THREE.PointsMaterial( {
+							color: color,
+							sizeAttenuation: false,
+							size: 2
+						} );
+
+					}
+
+					material = this._compareMaterials( material );
+					var points = new THREE.Points( geometry, material );
+					points.userData[ 'attributes' ] = attributes;
+					points.userData[ 'objectType' ] = obj.objectType;
+
+					if ( attributes.name ) {
+
+						points.name = attributes.name;
+
+					}
+
+					return points;
+
+				case 'Mesh':
+				case 'Extrusion':
+				case 'SubD':
+				case 'Brep':
+					if ( obj.geometry === null ) return;
+					var geometry = loader.parse( obj.geometry );
+
+					if ( geometry.attributes.hasOwnProperty( 'color' ) ) {
+
+						mat.vertexColors = true;
+
+					}
+
+					if ( mat === null ) {
+
+						mat = this._createMaterial();
+						mat = this._compareMaterials( mat );
+
+					}
+
+					var mesh = new THREE.Mesh( geometry, mat );
+					mesh.castShadow = attributes.castsShadows;
+					mesh.receiveShadow = attributes.receivesShadows;
+					mesh.userData[ 'attributes' ] = attributes;
+					mesh.userData[ 'objectType' ] = obj.objectType;
+
+					if ( attributes.name ) {
+
+						mesh.name = attributes.name;
+
+					}
+
+					return mesh;
+
+				case 'Curve':
+					geometry = loader.parse( obj.geometry );
+					var _color = attributes.drawColor;
+					var color = new THREE.Color( _color.r / 255.0, _color.g / 255.0, _color.b / 255.0 );
+					var material = new THREE.LineBasicMaterial( {
+						color: color
+					} );
+					material = this._compareMaterials( material );
+					var lines = new THREE.Line( geometry, material );
+					lines.userData[ 'attributes' ] = attributes;
+					lines.userData[ 'objectType' ] = obj.objectType;
+
+					if ( attributes.name ) {
+
+						lines.name = attributes.name;
+
+					}
+
+					return lines;
+
+				case 'TextDot':
+					geometry = obj.geometry;
+					var ctx = document.createElement( 'canvas' ).getContext( '2d' );
+					var font = `${geometry.fontHeight}px ${geometry.fontFace}`;
+					ctx.font = font;
+					var width = ctx.measureText( geometry.text ).width + 10;
+					var height = geometry.fontHeight + 10;
+					var r = window.devicePixelRatio;
+					ctx.canvas.width = width * r;
+					ctx.canvas.height = height * r;
+					ctx.canvas.style.width = width + 'px';
+					ctx.canvas.style.height = height + 'px';
+					ctx.setTransform( r, 0, 0, r, 0, 0 );
+					ctx.font = font;
+					ctx.textBaseline = 'middle';
+					ctx.textAlign = 'center';
+					var color = attributes.drawColor;
+					ctx.fillStyle = `rgba(${color.r},${color.g},${color.b},${color.a})`;
+					ctx.fillRect( 0, 0, width, height );
+					ctx.fillStyle = 'white';
+					ctx.fillText( geometry.text, width / 2, height / 2 );
+					var texture = new THREE.CanvasTexture( ctx.canvas );
+					texture.minFilter = THREE.LinearFilter;
+					texture.wrapS = THREE.ClampToEdgeWrapping;
+					texture.wrapT = THREE.ClampToEdgeWrapping;
+					var material = new THREE.SpriteMaterial( {
+						map: texture,
+						depthTest: false
+					} );
+					var sprite = new THREE.Sprite( material );
+					sprite.position.set( geometry.point[ 0 ], geometry.point[ 1 ], geometry.point[ 2 ] );
+					sprite.scale.set( width / 10, height / 10, 1.0 );
+					sprite.userData[ 'attributes' ] = attributes;
+					sprite.userData[ 'objectType' ] = obj.objectType;
+
+					if ( attributes.name ) {
+
+						sprite.name = attributes.name;
+
+					}
+
+					return sprite;
+
+				case 'Light':
+					geometry = obj.geometry;
+					var light;
+
+					if ( geometry.isDirectionalLight ) {
+
+						light = new THREE.DirectionalLight();
+						light.castShadow = attributes.castsShadows;
+						light.position.set( geometry.location[ 0 ], geometry.location[ 1 ], geometry.location[ 2 ] );
+						light.target.position.set( geometry.direction[ 0 ], geometry.direction[ 1 ], geometry.direction[ 2 ] );
+						light.shadow.normalBias = 0.1;
+
+					} else if ( geometry.isPointLight ) {
+
+						light = new THREE.PointLight();
+						light.castShadow = attributes.castsShadows;
+						light.position.set( geometry.location[ 0 ], geometry.location[ 1 ], geometry.location[ 2 ] );
+						light.shadow.normalBias = 0.1;
+
+					} else if ( geometry.isRectangularLight ) {
+
+						light = new THREE.RectAreaLight();
+						var width = Math.abs( geometry.width[ 2 ] );
+						var height = Math.abs( geometry.length[ 0 ] );
+						light.position.set( geometry.location[ 0 ] - height / 2, geometry.location[ 1 ], geometry.location[ 2 ] - width / 2 );
+						light.height = height;
+						light.width = width;
+						light.lookAt( new THREE.Vector3( geometry.direction[ 0 ], geometry.direction[ 1 ], geometry.direction[ 2 ] ) );
+
+					} else if ( geometry.isSpotLight ) {
+
+						light = new THREE.SpotLight();
+						light.castShadow = attributes.castsShadows;
+						light.position.set( geometry.location[ 0 ], geometry.location[ 1 ], geometry.location[ 2 ] );
+						light.target.position.set( geometry.direction[ 0 ], geometry.direction[ 1 ], geometry.direction[ 2 ] );
+						light.angle = geometry.spotAngleRadians;
+						light.shadow.normalBias = 0.1;
+
+					} else if ( geometry.isLinearLight ) {
+
+						console.warn( 'THREE.3DMLoader:	No conversion exists for linear lights.' );
+						return;
+
+					}
+
+					if ( light ) {
+
+						light.intensity = geometry.intensity;
+						var _color = geometry.diffuse;
+						var color = new THREE.Color( _color.r / 255.0, _color.g / 255.0, _color.b / 255.0 );
+						light.color = color;
+						light.userData[ 'attributes' ] = attributes;
+						light.userData[ 'objectType' ] = obj.objectType;
+
+					}
+
+					return light;
+
+			}
+
+		},
+		_initLibrary: function () {
+
+			if ( ! this.libraryPending ) {
+
+				// Load rhino3dm wrapper.
+				var jsLoader = new THREE.FileLoader( this.manager );
+				jsLoader.setPath( this.libraryPath );
+				var jsContent = new Promise( ( resolve, reject ) => {
+
+					jsLoader.load( 'rhino3dm.js', resolve, undefined, reject );
+
+				} ); // Load rhino3dm WASM binary.
+
+				var binaryLoader = new THREE.FileLoader( this.manager );
+				binaryLoader.setPath( this.libraryPath );
+				binaryLoader.setResponseType( 'arraybuffer' );
+				var binaryContent = new Promise( ( resolve, reject ) => {
+
+					binaryLoader.load( 'rhino3dm.wasm', resolve, undefined, reject );
+
+				} );
+				this.libraryPending = Promise.all( [ jsContent, binaryContent ] ).then( ( [ jsContent, binaryContent ] ) => {
+
+					//this.libraryBinary = binaryContent;
+					this.libraryConfig.wasmBinary = binaryContent;
+					var fn = Rhino3dmLoader.Rhino3dmWorker.toString();
+					var body = [ '/* rhino3dm.js */', jsContent, '/* worker */', fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) ].join( '\n' );
+					this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) );
+
+				} );
+
+			}
+
+			return this.libraryPending;
+
+		},
+		_getWorker: function ( taskCost ) {
+
+			return this._initLibrary().then( () => {
+
+				if ( this.workerPool.length < this.workerLimit ) {
+
+					var worker = new Worker( this.workerSourceURL );
+					worker._callbacks = {};
+					worker._taskCosts = {};
+					worker._taskLoad = 0;
+					worker.postMessage( {
+						type: 'init',
+						libraryConfig: this.libraryConfig
+					} );
+
+					worker.onmessage = function ( e ) {
+
+						var message = e.data;
+
+						switch ( message.type ) {
+
+							case 'decode':
+								worker._callbacks[ message.id ].resolve( message );
+
+								break;
+
+							case 'error':
+								worker._callbacks[ message.id ].reject( message );
+
+								break;
+
+							default:
+								console.error( 'THREE.Rhino3dmLoader: Unexpected message, "' + message.type + '"' );
+
+						}
+
+					};
+
+					this.workerPool.push( worker );
+
+				} else {
+
+					this.workerPool.sort( function ( a, b ) {
+
+						return a._taskLoad > b._taskLoad ? - 1 : 1;
+
+					} );
+
+				}
+
+				var worker = this.workerPool[ this.workerPool.length - 1 ];
+				worker._taskLoad += taskCost;
+				return worker;
+
+			} );
+
+		},
+		_releaseTask: function ( worker, taskID ) {
+
+			worker._taskLoad -= worker._taskCosts[ taskID ];
+			delete worker._callbacks[ taskID ];
+			delete worker._taskCosts[ taskID ];
+
+		},
+		dispose: function () {
+
+			for ( var i = 0; i < this.workerPool.length; ++ i ) {
+
+				this.workerPool[ i ].terminate();
+
+			}
+
+			this.workerPool.length = 0;
+			return this;
+
+		}
+	} );
+	/* WEB WORKER */
+
+	Rhino3dmLoader.Rhino3dmWorker = function () {
+
+		var libraryPending;
+		var libraryConfig;
+		var rhino;
+
+		onmessage = function ( e ) {
+
+			var message = e.data;
+
+			switch ( message.type ) {
+
+				case 'init':
+					libraryConfig = message.libraryConfig;
+					var wasmBinary = libraryConfig.wasmBinary;
+					var RhinoModule;
+					libraryPending = new Promise( function ( resolve ) {
+
+						/* Like Basis THREE.Loader */
+						RhinoModule = {
+							wasmBinary,
+							onRuntimeInitialized: resolve
+						};
+						rhino3dm( RhinoModule ); // eslint-disable-line no-undef
+
+					} ).then( () => {
+
+						rhino = RhinoModule;
+
+					} );
+					break;
+
+				case 'decode':
+					var buffer = message.buffer;
+					libraryPending.then( () => {
+
+						var data = decodeObjects( rhino, buffer );
+						self.postMessage( {
+							type: 'decode',
+							id: message.id,
+							data
+						} );
+
+					} );
+					break;
+
+			}
+
+		};
+
+		function decodeObjects( rhino, buffer ) {
+
+			var arr = new Uint8Array( buffer );
+			var doc = rhino.File3dm.fromByteArray( arr );
+			var objects = [];
+			var materials = [];
+			var layers = [];
+			var views = [];
+			var namedViews = [];
+			var groups = []; //Handle objects
+
+			var objs = doc.objects();
+			var cnt = objs.count;
+
+			for ( var i = 0; i < cnt; i ++ ) {
+
+				var _object = objs.get( i );
+
+				var object = extractObjectData( _object, doc );
+
+				_object.delete();
+
+				if ( object ) {
+
+					objects.push( object );
+
+				}
+
+			} // Handle instance definitions
+			// console.log( `Instance Definitions Count: ${doc.instanceDefinitions().count()}` );
+
+
+			for ( var i = 0; i < doc.instanceDefinitions().count(); i ++ ) {
+
+				var idef = doc.instanceDefinitions().get( i );
+				var idefAttributes = extractProperties( idef );
+				idefAttributes.objectIds = idef.getObjectIds();
+				objects.push( {
+					geometry: null,
+					attributes: idefAttributes,
+					objectType: 'InstanceDefinition'
+				} );
+
+			} // Handle materials
+
+
+			var textureTypes = [// rhino.TextureType.Bitmap,
+				rhino.TextureType.Diffuse, rhino.TextureType.Bump, rhino.TextureType.Transparency, rhino.TextureType.Opacity, rhino.TextureType.Emap ];
+			var pbrTextureTypes = [ rhino.TextureType.PBR_BaseColor, rhino.TextureType.PBR_Subsurface, rhino.TextureType.PBR_SubsurfaceScattering, rhino.TextureType.PBR_SubsurfaceScatteringRadius, rhino.TextureType.PBR_Metallic, rhino.TextureType.PBR_Specular, rhino.TextureType.PBR_SpecularTint, rhino.TextureType.PBR_Roughness, rhino.TextureType.PBR_Anisotropic, rhino.TextureType.PBR_Anisotropic_Rotation, rhino.TextureType.PBR_Sheen, rhino.TextureType.PBR_SheenTint, rhino.TextureType.PBR_Clearcoat, rhino.TextureType.PBR_ClearcoatBump, rhino.TextureType.PBR_ClearcoatRoughness, rhino.TextureType.PBR_OpacityIor, rhino.TextureType.PBR_OpacityRoughness, rhino.TextureType.PBR_Emission, rhino.TextureType.PBR_AmbientOcclusion, rhino.TextureType.PBR_Displacement ];
+
+			for ( var i = 0; i < doc.materials().count(); i ++ ) {
+
+				var _material = doc.materials().get( i );
+
+				var _pbrMaterial = _material.physicallyBased();
+
+				var material = extractProperties( _material );
+				var textures = [];
+
+				for ( var j = 0; j < textureTypes.length; j ++ ) {
+
+					var _texture = _material.getTexture( textureTypes[ j ] );
+
+					if ( _texture ) {
+
+						var textureType = textureTypes[ j ].constructor.name;
+						textureType = textureType.substring( 12, textureType.length );
+						var texture = {
+							type: textureType
+						};
+						var image = doc.getEmbeddedFileAsBase64( _texture.fileName );
+
+						if ( image ) {
+
+							texture.image = 'data:image/png;base64,' + image;
+
+						} else {
+
+							console.warn( `THREE.3DMLoader: Image for ${textureType} texture not embedded in file.` );
+							texture.image = null;
+
+						}
+
+						textures.push( texture );
+
+						_texture.delete();
+
+					}
+
+				}
+
+				material.textures = textures;
+
+				if ( _pbrMaterial.supported ) {
+
+					console.log( 'pbr true' );
+
+					for ( var j = 0; j < pbrTextureTypes.length; j ++ ) {
+
+						var _texture = _material.getTexture( textureTypes[ j ] );
+
+						if ( _texture ) {
+
+							var image = doc.getEmbeddedFileAsBase64( _texture.fileName );
+							var textureType = textureTypes[ j ].constructor.name;
+							textureType = textureType.substring( 12, textureType.length );
+							var texture = {
+								type: textureType,
+								image: 'data:image/png;base64,' + image
+							};
+							textures.push( texture );
+
+							_texture.delete();
+
+						}
+
+					}
+
+					var pbMaterialProperties = extractProperties( _material.physicallyBased() );
+					material = Object.assign( pbMaterialProperties, material );
+
+				}
+
+				materials.push( material );
+
+				_material.delete();
+
+				_pbrMaterial.delete();
+
+			} // Handle layers
+
+
+			for ( var i = 0; i < doc.layers().count(); i ++ ) {
+
+				var _layer = doc.layers().get( i );
+
+				var layer = extractProperties( _layer );
+				layers.push( layer );
+
+				_layer.delete();
+
+			} // Handle views
+
+
+			for ( var i = 0; i < doc.views().count(); i ++ ) {
+
+				var _view = doc.views().get( i );
+
+				var view = extractProperties( _view );
+				views.push( view );
+
+				_view.delete();
+
+			} // Handle named views
+
+
+			for ( var i = 0; i < doc.namedViews().count(); i ++ ) {
+
+				var _namedView = doc.namedViews().get( i );
+
+				var namedView = extractProperties( _namedView );
+				namedViews.push( namedView );
+
+				_namedView.delete();
+
+			} // Handle groups
+
+
+			for ( var i = 0; i < doc.groups().count(); i ++ ) {
+
+				var _group = doc.groups().get( i );
+
+				var group = extractProperties( _group );
+				groups.push( group );
+
+				_group.delete();
+
+			} // Handle settings
+
+
+			var settings = extractProperties( doc.settings() ); //TODO: Handle other document stuff like dimstyles, instance definitions, bitmaps etc.
+			// Handle dimstyles
+			// console.log( `Dimstyle Count: ${doc.dimstyles().count()}` );
+			// Handle bitmaps
+			// console.log( `Bitmap Count: ${doc.bitmaps().count()}` );
+			// Handle strings -- this seems to be broken at the moment in rhino3dm
+			// console.log( `Document Strings Count: ${doc.strings().count()}` );
+
+			/*
+		for( var i = 0; i < doc.strings().count(); i++ ){
+				var _string= doc.strings().get( i );
+				console.log(_string);
+			var string = extractProperties( _group );
+				strings.push( string );
+				_string.delete();
+			}
+		*/
+
+			doc.delete();
+			return {
+				objects,
+				materials,
+				layers,
+				views,
+				namedViews,
+				groups,
+				settings
+			};
+
+		}
+
+		function extractObjectData( object, doc ) {
+
+			var _geometry = object.geometry();
+
+			var _attributes = object.attributes();
+
+			var objectType = _geometry.objectType;
+			var geometry = null;
+			var attributes = null; // skip instance definition objects
+			//if( _attributes.isInstanceDefinitionObject ) { continue; }
+			// TODO: handle other geometry types
+
+			switch ( objectType ) {
+
+				case rhino.ObjectType.Curve:
+					var pts = curveToPoints( _geometry, 100 );
+					var position = {};
+					var attributes = {};
+					var data = {};
+					position.itemSize = 3;
+					position.type = 'Float32Array';
+					position.array = [];
+
+					for ( var j = 0; j < pts.length; j ++ ) {
+
+						position.array.push( pts[ j ][ 0 ] );
+						position.array.push( pts[ j ][ 1 ] );
+						position.array.push( pts[ j ][ 2 ] );
+
+					}
+
+					attributes.position = position;
+					data.attributes = attributes;
+					geometry = {
+						data
+					};
+					break;
+
+				case rhino.ObjectType.Point:
+					var pt = _geometry.location;
+					var position = {};
+					var color = {};
+					var attributes = {};
+					var data = {};
+					position.itemSize = 3;
+					position.type = 'Float32Array';
+					position.array = [ pt[ 0 ], pt[ 1 ], pt[ 2 ] ];
+
+					var _color = _attributes.drawColor( doc );
+
+					color.itemSize = 3;
+					color.type = 'Float32Array';
+					color.array = [ _color.r / 255.0, _color.g / 255.0, _color.b / 255.0 ];
+					attributes.position = position;
+					attributes.color = color;
+					data.attributes = attributes;
+					geometry = {
+						data
+					};
+					break;
+
+				case rhino.ObjectType.PointSet:
+				case rhino.ObjectType.Mesh:
+					geometry = _geometry.toThreejsJSON();
+					break;
+
+				case rhino.ObjectType.Brep:
+					var faces = _geometry.faces();
+
+					var mesh = new rhino.Mesh();
+
+					for ( var faceIndex = 0; faceIndex < faces.count; faceIndex ++ ) {
+
+						var face = faces.get( faceIndex );
+
+						var _mesh = face.getMesh( rhino.MeshType.Any );
+
+						if ( _mesh ) {
+
+							mesh.append( _mesh );
+
+							_mesh.delete();
+
+						}
+
+						face.delete();
+
+					}
+
+					if ( mesh.faces().count > 0 ) {
+
+						mesh.compact();
+						geometry = mesh.toThreejsJSON();
+						faces.delete();
+
+					}
+
+					mesh.delete();
+					break;
+
+				case rhino.ObjectType.Extrusion:
+					var mesh = _geometry.getMesh( rhino.MeshType.Any );
+
+					if ( mesh ) {
+
+						geometry = mesh.toThreejsJSON();
+						mesh.delete();
+
+					}
+
+					break;
+
+				case rhino.ObjectType.TextDot:
+					geometry = extractProperties( _geometry );
+					break;
+
+				case rhino.ObjectType.Light:
+					geometry = extractProperties( _geometry );
+					break;
+
+				case rhino.ObjectType.InstanceReference:
+					geometry = extractProperties( _geometry );
+					geometry.xform = extractProperties( _geometry.xform );
+					geometry.xform.array = _geometry.xform.toFloatArray( true );
+					break;
+
+				case rhino.ObjectType.SubD:
+				// TODO: precalculate resulting vertices and faces and warn on excessive results
+					_geometry.subdivide( 3 );
+
+					var mesh = rhino.Mesh.createFromSubDControlNet( _geometry );
+
+					if ( mesh ) {
+
+						geometry = mesh.toThreejsJSON();
+						mesh.delete();
+
+					}
+
+					break;
+
+					/*
+			case rhino.ObjectType.Annotation:
+			case rhino.ObjectType.Hatch:
+			case rhino.ObjectType.ClipPlane:
+			*/
+
+				default:
+					console.warn( `THREE.3DMLoader: TODO: Implement ${objectType.constructor.name}` );
+					break;
+
+			}
+
+			if ( geometry ) {
+
+				var attributes = extractProperties( _attributes );
+				attributes.geometry = extractProperties( _geometry );
+
+				if ( _attributes.groupCount > 0 ) {
+
+					attributes.groupIds = _attributes.getGroupList();
+
+				}
+
+				if ( _attributes.userStringCount > 0 ) {
+
+					attributes.userStrings = _attributes.getUserStrings();
+
+				}
+
+				if ( _geometry.userStringCount > 0 ) {
+
+					attributes.geometry.userStrings = _geometry.getUserStrings();
+
+				}
+
+				attributes.drawColor = _attributes.drawColor( doc );
+				objectType = objectType.constructor.name;
+				objectType = objectType.substring( 11, objectType.length );
+				return {
+					geometry,
+					attributes,
+					objectType
+				};
+
+			} else {
+
+				console.warn( `THREE.3DMLoader: ${objectType.constructor.name} has no associated mesh geometry.` );
+
+			}
+
+		}
+
+		function extractProperties( object ) {
+
+			var result = {};
+
+			for ( var property in object ) {
+
+				var value = object[ property ];
+
+				if ( typeof value !== 'function' ) {
+
+					if ( typeof value === 'object' && value !== null && value.hasOwnProperty( 'constructor' ) ) {
+
+						result[ property ] = {
+							name: value.constructor.name,
+							value: value.value
+						};
+
+					} else {
+
+						result[ property ] = value;
+
+					}
+
+				} else { // these are functions that could be called to extract more data.
+				//console.log( `${property}: ${object[ property ].constructor.name}` );
+				}
+
+			}
+
+			return result;
+
+		}
+
+		function curveToPoints( curve, pointLimit ) {
+
+			var pointCount = pointLimit;
+			var rc = [];
+			var ts = [];
+
+			if ( curve instanceof rhino.LineCurve ) {
+
+				return [ curve.pointAtStart, curve.pointAtEnd ];
+
+			}
+
+			if ( curve instanceof rhino.PolylineCurve ) {
+
+				pointCount = curve.pointCount;
+
+				for ( var i = 0; i < pointCount; i ++ ) {
+
+					rc.push( curve.point( i ) );
+
+				}
+
+				return rc;
+
+			}
+
+			if ( curve instanceof rhino.PolyCurve ) {
+
+				var segmentCount = curve.segmentCount;
+
+				for ( var i = 0; i < segmentCount; i ++ ) {
+
+					var segment = curve.segmentCurve( i );
+					var segmentArray = curveToPoints( segment, pointCount );
+					rc = rc.concat( segmentArray );
+					segment.delete();
+
+				}
+
+				return rc;
+
+			}
+
+			if ( curve instanceof rhino.ArcCurve ) {
+
+				pointCount = Math.floor( curve.angleDegrees / 5 );
+				pointCount = pointCount < 2 ? 2 : pointCount; // alternative to this hardcoded version: https://stackoverflow.com/a/18499923/2179399
+
+			}
+
+			if ( curve instanceof rhino.NurbsCurve && curve.degree === 1 ) {
+
+				const pLine = curve.tryGetPolyline();
+
+				for ( var i = 0; i < pLine.count; i ++ ) {
+
+					rc.push( pLine.get( i ) );
+
+				}
+
+				pLine.delete();
+				return rc;
+
+			}
+
+			var domain = curve.domain;
+			var divisions = pointCount - 1.0;
+
+			for ( var j = 0; j < pointCount; j ++ ) {
+
+				var t = domain[ 0 ] + j / divisions * ( domain[ 1 ] - domain[ 0 ] );
+
+				if ( t === domain[ 0 ] || t === domain[ 1 ] ) {
+
+					ts.push( t );
+					continue;
+
+				}
+
+				var tan = curve.tangentAt( t );
+				var prevTan = curve.tangentAt( ts.slice( - 1 )[ 0 ] ); // Duplicated from THREE.Vector3
+				// How to pass imports to worker?
+
+				var tS = tan[ 0 ] * tan[ 0 ] + tan[ 1 ] * tan[ 1 ] + tan[ 2 ] * tan[ 2 ];
+				var ptS = prevTan[ 0 ] * prevTan[ 0 ] + prevTan[ 1 ] * prevTan[ 1 ] + prevTan[ 2 ] * prevTan[ 2 ];
+				var denominator = Math.sqrt( tS * ptS );
+				var angle;
+
+				if ( denominator === 0 ) {
+
+					angle = Math.PI / 2;
+
+				} else {
+
+					var theta = ( tan.x * prevTan.x + tan.y * prevTan.y + tan.z * prevTan.z ) / denominator;
+					angle = Math.acos( Math.max( - 1, Math.min( 1, theta ) ) );
+
+				}
+
+				if ( angle < 0.1 ) continue;
+				ts.push( t );
+
+			}
+
+			rc = ts.map( t => curve.pointAt( t ) );
+			return rc;
+
+		}
+
+	};
+
+	THREE.Rhino3dmLoader = Rhino3dmLoader;
+
+} )();

+ 797 - 909
examples/js/loaders/3MFLoader.js

@@ -1,4 +1,6 @@
-/**
+( function () {
+
+	/**
  *
  * 3D Manufacturing Format (3MF) specification: https://3mf.io/specification/
  *
@@ -12,1435 +14,1321 @@
  *
  * - Texture 2D
  * - Texture 2D Groups
- * - Color Groups (Vertex Colors)
+ * - THREE.Color Groups (Vertex Colors)
  * - Metallic Display Properties (PBR)
  */
 
-THREE.ThreeMFLoader = function ( manager ) {
-
-	THREE.Loader.call( this, manager );
+	var ThreeMFLoader = function ( manager ) {
 
-	this.availableExtensions = [];
+		THREE.Loader.call( this, manager );
+		this.availableExtensions = [];
 
-};
+	};
 
-THREE.ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+	ThreeMFLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+		constructor: ThreeMFLoader,
+		load: function ( url, onLoad, onProgress, onError ) {
 
-	constructor: THREE.ThreeMFLoader,
+			var scope = this;
+			var loader = new THREE.FileLoader( scope.manager );
+			loader.setPath( scope.path );
+			loader.setResponseType( 'arraybuffer' );
+			loader.setRequestHeader( scope.requestHeader );
+			loader.setWithCredentials( scope.withCredentials );
+			loader.load( url, function ( buffer ) {
 
-	load: function ( url, onLoad, onProgress, onError ) {
+				try {
 
-		var scope = this;
-		var loader = new THREE.FileLoader( scope.manager );
-		loader.setPath( scope.path );
-		loader.setResponseType( 'arraybuffer' );
-		loader.setRequestHeader( scope.requestHeader );
-		loader.setWithCredentials( scope.withCredentials );
-		loader.load( url, function ( buffer ) {
+					onLoad( scope.parse( buffer ) );
 
-			try {
+				} catch ( e ) {
 
-				onLoad( scope.parse( buffer ) );
+					if ( onError ) {
 
-			} catch ( e ) {
+						onError( e );
 
-				if ( onError ) {
+					} else {
 
-					onError( e );
+						console.error( e );
 
-				} else {
+					}
 
-					console.error( e );
+					scope.manager.itemError( url );
 
 				}
 
-				scope.manager.itemError( url );
-
-			}
-
-		}, onProgress, onError );
-
-	},
+			}, onProgress, onError );
 
-	parse: function ( data ) {
+		},
+		parse: function ( data ) {
 
-		var scope = this;
-		var textureLoader = new THREE.TextureLoader( this.manager );
+			var scope = this;
+			var textureLoader = new THREE.TextureLoader( this.manager );
 
-		function loadDocument( data ) {
+			function loadDocument( data ) {
 
-			var zip = null;
-			var file = null;
+				var zip = null;
+				var file = null;
+				var relsName;
+				var modelRelsName;
+				var modelPartNames = [];
+				var printTicketPartNames = [];
+				var texturesPartNames = [];
+				var otherPartNames = [];
+				var rels;
+				var modelRels;
+				var modelParts = {};
+				var printTicketParts = {};
+				var texturesParts = {};
+				var otherParts = {};
 
-			var relsName;
-			var modelRelsName;
-			var modelPartNames = [];
-			var printTicketPartNames = [];
-			var texturesPartNames = [];
-			var otherPartNames = [];
+				try {
 
-			var rels;
-			var modelRels;
-			var modelParts = {};
-			var printTicketParts = {};
-			var texturesParts = {};
-			var otherParts = {};
+					zip = fflate.unzipSync( new Uint8Array( data ) ); // eslint-disable-line no-undef
 
-			try {
+				} catch ( e ) {
 
-				zip = fflate.unzipSync( new Uint8Array( data ) ); // eslint-disable-line no-undef
+					if ( e instanceof ReferenceError ) {
 
-			} catch ( e ) {
+						console.error( 'THREE.3MFLoader: fflate missing and file is compressed.' );
+						return null;
 
-				if ( e instanceof ReferenceError ) {
-
-					console.error( 'THREE.3MFLoader: fflate missing and file is compressed.' );
-					return null;
+					}
 
 				}
 
-			}
-
-			for ( file in zip ) {
+				for ( file in zip ) {
 
-				if ( file.match( /\_rels\/.rels$/ ) ) {
+					if ( file.match( /\_rels\/.rels$/ ) ) {
 
-					relsName = file;
+						relsName = file;
 
-				} else if ( file.match( /3D\/_rels\/.*\.model\.rels$/ ) ) {
+					} else if ( file.match( /3D\/_rels\/.*\.model\.rels$/ ) ) {
 
-					modelRelsName = file;
+						modelRelsName = file;
 
-				} else if ( file.match( /^3D\/.*\.model$/ ) ) {
+					} else if ( file.match( /^3D\/.*\.model$/ ) ) {
 
-					modelPartNames.push( file );
+						modelPartNames.push( file );
 
-				} else if ( file.match( /^3D\/Metadata\/.*\.xml$/ ) ) {
+					} else if ( file.match( /^3D\/Metadata\/.*\.xml$/ ) ) {
 
-					printTicketPartNames.push( file );
+						printTicketPartNames.push( file );
 
-				} else if ( file.match( /^3D\/Textures?\/.*/ ) ) {
+					} else if ( file.match( /^3D\/Textures?\/.*/ ) ) {
 
-					texturesPartNames.push( file );
+						texturesPartNames.push( file );
 
-				} else if ( file.match( /^3D\/Other\/.*/ ) ) {
+					} else if ( file.match( /^3D\/Other\/.*/ ) ) {
 
-					otherPartNames.push( file );
-
-				}
+						otherPartNames.push( file );
 
-			}
-
-			//
-
-			var relsView = zip[ relsName ];
-			var relsFileText = THREE.LoaderUtils.decodeText( relsView );
-			rels = parseRelsXml( relsFileText );
+					}
 
-			//
+				} //
 
-			if ( modelRelsName ) {
 
-				var relsView = zip[ modelRelsName ];
+				var relsView = zip[ relsName ];
 				var relsFileText = THREE.LoaderUtils.decodeText( relsView );
-				modelRels = parseRelsXml( relsFileText );
+				rels = parseRelsXml( relsFileText ); //
 
-			}
-
-			//
-
-			for ( var i = 0; i < modelPartNames.length; i ++ ) {
+				if ( modelRelsName ) {
 
-				var modelPart = modelPartNames[ i ];
-				var view = zip[ modelPart ];
+					var relsView = zip[ modelRelsName ];
+					var relsFileText = THREE.LoaderUtils.decodeText( relsView );
+					modelRels = parseRelsXml( relsFileText );
 
-				var fileText = THREE.LoaderUtils.decodeText( view );
-				var xmlData = new DOMParser().parseFromString( fileText, 'application/xml' );
+				} //
 
-				if ( xmlData.documentElement.nodeName.toLowerCase() !== 'model' ) {
 
-					console.error( 'THREE.3MFLoader: Error loading 3MF - no 3MF document found: ', modelPart );
-
-				}
+				for ( var i = 0; i < modelPartNames.length; i ++ ) {
 
-				var modelNode = xmlData.querySelector( 'model' );
-				var extensions = {};
+					var modelPart = modelPartNames[ i ];
+					var view = zip[ modelPart ];
+					var fileText = THREE.LoaderUtils.decodeText( view );
+					var xmlData = new DOMParser().parseFromString( fileText, 'application/xml' );
 
-				for ( var i = 0; i < modelNode.attributes.length; i ++ ) {
+					if ( xmlData.documentElement.nodeName.toLowerCase() !== 'model' ) {
 
-					var attr = modelNode.attributes[ i ];
-					if ( attr.name.match( /^xmlns:(.+)$/ ) ) {
-
-						extensions[ attr.value ] = RegExp.$1;
+						console.error( 'THREE.3MFLoader: Error loading 3MF - no 3MF document found: ', modelPart );
 
 					}
 
-				}
-
-				var modelData = parseModelNode( modelNode );
-				modelData[ 'xml' ] = modelNode;
-
-				if ( 0 < Object.keys( extensions ).length ) {
+					var modelNode = xmlData.querySelector( 'model' );
+					var extensions = {};
 
-					modelData[ 'extensions' ] = extensions;
+					for ( var i = 0; i < modelNode.attributes.length; i ++ ) {
 
-				}
+						var attr = modelNode.attributes[ i ];
 
-				modelParts[ modelPart ] = modelData;
+						if ( attr.name.match( /^xmlns:(.+)$/ ) ) {
 
-			}
+							extensions[ attr.value ] = RegExp.$1;
 
-			//
+						}
 
-			for ( var i = 0; i < texturesPartNames.length; i ++ ) {
+					}
 
-				var texturesPartName = texturesPartNames[ i ];
-				texturesParts[ texturesPartName ] = zip[ texturesPartName ].buffer;
+					var modelData = parseModelNode( modelNode );
+					modelData[ 'xml' ] = modelNode;
 
-			}
+					if ( 0 < Object.keys( extensions ).length ) {
 
-			return {
-				rels: rels,
-				modelRels: modelRels,
-				model: modelParts,
-				printTicket: printTicketParts,
-				texture: texturesParts,
-				other: otherParts
-			};
+						modelData[ 'extensions' ] = extensions;
 
-		}
+					}
 
-		function parseRelsXml( relsFileText ) {
+					modelParts[ modelPart ] = modelData;
 
-			var relationships = [];
+				} //
 
-			var relsXmlData = new DOMParser().parseFromString( relsFileText, 'application/xml' );
 
-			var relsNodes = relsXmlData.querySelectorAll( 'Relationship' );
+				for ( var i = 0; i < texturesPartNames.length; i ++ ) {
 
-			for ( var i = 0; i < relsNodes.length; i ++ ) {
+					var texturesPartName = texturesPartNames[ i ];
+					texturesParts[ texturesPartName ] = zip[ texturesPartName ].buffer;
 
-				var relsNode = relsNodes[ i ];
+				}
 
-				var relationship = {
-					target: relsNode.getAttribute( 'Target' ), //required
-					id: relsNode.getAttribute( 'Id' ), //required
-					type: relsNode.getAttribute( 'Type' ) //required
+				return {
+					rels: rels,
+					modelRels: modelRels,
+					model: modelParts,
+					printTicket: printTicketParts,
+					texture: texturesParts,
+					other: otherParts
 				};
 
-				relationships.push( relationship );
-
 			}
 
-			return relationships;
+			function parseRelsXml( relsFileText ) {
 
-		}
+				var relationships = [];
+				var relsXmlData = new DOMParser().parseFromString( relsFileText, 'application/xml' );
+				var relsNodes = relsXmlData.querySelectorAll( 'Relationship' );
 
-		function parseMetadataNodes( metadataNodes ) {
+				for ( var i = 0; i < relsNodes.length; i ++ ) {
 
-			var metadataData = {};
+					var relsNode = relsNodes[ i ];
+					var relationship = {
+						target: relsNode.getAttribute( 'Target' ),
+						//required
+						id: relsNode.getAttribute( 'Id' ),
+						//required
+						type: relsNode.getAttribute( 'Type' ) //required
 
-			for ( var i = 0; i < metadataNodes.length; i ++ ) {
+					};
+					relationships.push( relationship );
 
-				var metadataNode = metadataNodes[ i ];
-				var name = metadataNode.getAttribute( 'name' );
-				var validNames = [
-					'Title',
-					'Designer',
-					'Description',
-					'Copyright',
-					'LicenseTerms',
-					'Rating',
-					'CreationDate',
-					'ModificationDate'
-				];
+				}
 
-				if ( 0 <= validNames.indexOf( name ) ) {
+				return relationships;
 
-					metadataData[ name ] = metadataNode.textContent;
+			}
 
-				}
+			function parseMetadataNodes( metadataNodes ) {
 
-			}
+				var metadataData = {};
 
-			return metadataData;
+				for ( var i = 0; i < metadataNodes.length; i ++ ) {
 
-		}
+					var metadataNode = metadataNodes[ i ];
+					var name = metadataNode.getAttribute( 'name' );
+					var validNames = [ 'Title', 'Designer', 'Description', 'Copyright', 'LicenseTerms', 'Rating', 'CreationDate', 'ModificationDate' ];
 
-		function parseBasematerialsNode( basematerialsNode ) {
+					if ( 0 <= validNames.indexOf( name ) ) {
 
-			var basematerialsData = {
-				id: basematerialsNode.getAttribute( 'id' ), // required
-				basematerials: []
-			};
+						metadataData[ name ] = metadataNode.textContent;
 
-			var basematerialNodes = basematerialsNode.querySelectorAll( 'base' );
+					}
 
-			for ( var i = 0; i < basematerialNodes.length; i ++ ) {
+				}
 
-				var basematerialNode = basematerialNodes[ i ];
-				var basematerialData = parseBasematerialNode( basematerialNode );
-				basematerialData.index = i; // the order and count of the material nodes form an implicit 0-based index
-				basematerialsData.basematerials.push( basematerialData );
+				return metadataData;
 
 			}
 
-			return basematerialsData;
+			function parseBasematerialsNode( basematerialsNode ) {
 
-		}
+				var basematerialsData = {
+					id: basematerialsNode.getAttribute( 'id' ),
+					// required
+					basematerials: []
+				};
+				var basematerialNodes = basematerialsNode.querySelectorAll( 'base' );
 
-		function parseTexture2DNode( texture2DNode ) {
+				for ( var i = 0; i < basematerialNodes.length; i ++ ) {
 
-			var texture2dData = {
-				id: texture2DNode.getAttribute( 'id' ), // required
-				path: texture2DNode.getAttribute( 'path' ), // required
-				contenttype: texture2DNode.getAttribute( 'contenttype' ), // required
-				tilestyleu: texture2DNode.getAttribute( 'tilestyleu' ),
-				tilestylev: texture2DNode.getAttribute( 'tilestylev' ),
-				filter: texture2DNode.getAttribute( 'filter' ),
-			};
+					var basematerialNode = basematerialNodes[ i ];
+					var basematerialData = parseBasematerialNode( basematerialNode );
+					basematerialData.index = i; // the order and count of the material nodes form an implicit 0-based index
 
-			return texture2dData;
+					basematerialsData.basematerials.push( basematerialData );
 
-		}
+				}
 
-		function parseTextures2DGroupNode( texture2DGroupNode ) {
+				return basematerialsData;
 
-			var texture2DGroupData = {
-				id: texture2DGroupNode.getAttribute( 'id' ), // required
-				texid: texture2DGroupNode.getAttribute( 'texid' ), // required
-				displaypropertiesid: texture2DGroupNode.getAttribute( 'displaypropertiesid' )
-			};
+			}
 
-			var tex2coordNodes = texture2DGroupNode.querySelectorAll( 'tex2coord' );
+			function parseTexture2DNode( texture2DNode ) {
+
+				var texture2dData = {
+					id: texture2DNode.getAttribute( 'id' ),
+					// required
+					path: texture2DNode.getAttribute( 'path' ),
+					// required
+					contenttype: texture2DNode.getAttribute( 'contenttype' ),
+					// required
+					tilestyleu: texture2DNode.getAttribute( 'tilestyleu' ),
+					tilestylev: texture2DNode.getAttribute( 'tilestylev' ),
+					filter: texture2DNode.getAttribute( 'filter' )
+				};
+				return texture2dData;
 
-			var uvs = [];
+			}
 
-			for ( var i = 0; i < tex2coordNodes.length; i ++ ) {
+			function parseTextures2DGroupNode( texture2DGroupNode ) {
 
-				var tex2coordNode = tex2coordNodes[ i ];
-				var u = tex2coordNode.getAttribute( 'u' );
-				var v = tex2coordNode.getAttribute( 'v' );
+				var texture2DGroupData = {
+					id: texture2DGroupNode.getAttribute( 'id' ),
+					// required
+					texid: texture2DGroupNode.getAttribute( 'texid' ),
+					// required
+					displaypropertiesid: texture2DGroupNode.getAttribute( 'displaypropertiesid' )
+				};
+				var tex2coordNodes = texture2DGroupNode.querySelectorAll( 'tex2coord' );
+				var uvs = [];
 
-				uvs.push( parseFloat( u ), parseFloat( v ) );
+				for ( var i = 0; i < tex2coordNodes.length; i ++ ) {
 
-			}
+					var tex2coordNode = tex2coordNodes[ i ];
+					var u = tex2coordNode.getAttribute( 'u' );
+					var v = tex2coordNode.getAttribute( 'v' );
+					uvs.push( parseFloat( u ), parseFloat( v ) );
 
-			texture2DGroupData[ 'uvs' ] = new Float32Array( uvs );
+				}
 
-			return texture2DGroupData;
+				texture2DGroupData[ 'uvs' ] = new Float32Array( uvs );
+				return texture2DGroupData;
 
-		}
-
-		function parseColorGroupNode( colorGroupNode ) {
+			}
 
-			var colorGroupData = {
-				id: colorGroupNode.getAttribute( 'id' ), // required
-				displaypropertiesid: colorGroupNode.getAttribute( 'displaypropertiesid' )
-			};
+			function parseColorGroupNode( colorGroupNode ) {
 
-			var colorNodes = colorGroupNode.querySelectorAll( 'color' );
+				var colorGroupData = {
+					id: colorGroupNode.getAttribute( 'id' ),
+					// required
+					displaypropertiesid: colorGroupNode.getAttribute( 'displaypropertiesid' )
+				};
+				var colorNodes = colorGroupNode.querySelectorAll( 'color' );
+				var colors = [];
+				var colorObject = new THREE.Color();
 
-			var colors = [];
-			var colorObject = new THREE.Color();
+				for ( var i = 0; i < colorNodes.length; i ++ ) {
 
-			for ( var i = 0; i < colorNodes.length; i ++ ) {
+					var colorNode = colorNodes[ i ];
+					var color = colorNode.getAttribute( 'color' );
+					colorObject.setStyle( color.substring( 0, 7 ) );
+					colorObject.convertSRGBToLinear(); // color is in sRGB
 
-				var colorNode = colorNodes[ i ];
-				var color = colorNode.getAttribute( 'color' );
+					colors.push( colorObject.r, colorObject.g, colorObject.b );
 
-				colorObject.setStyle( color.substring( 0, 7 ) );
-				colorObject.convertSRGBToLinear(); // color is in sRGB
+				}
 
-				colors.push( colorObject.r, colorObject.g, colorObject.b );
+				colorGroupData[ 'colors' ] = new Float32Array( colors );
+				return colorGroupData;
 
 			}
 
-			colorGroupData[ 'colors' ] = new Float32Array( colors );
+			function parseMetallicDisplaypropertiesNode( metallicDisplaypropetiesNode ) {
 
-			return colorGroupData;
+				var metallicDisplaypropertiesData = {
+					id: metallicDisplaypropetiesNode.getAttribute( 'id' ) // required
 
-		}
-
-		function parseMetallicDisplaypropertiesNode( metallicDisplaypropetiesNode ) {
-
-			var metallicDisplaypropertiesData = {
-				id: metallicDisplaypropetiesNode.getAttribute( 'id' ) // required
-			};
+				};
+				var metallicNodes = metallicDisplaypropetiesNode.querySelectorAll( 'pbmetallic' );
+				var metallicData = [];
 
-			var metallicNodes = metallicDisplaypropetiesNode.querySelectorAll( 'pbmetallic' );
+				for ( var i = 0; i < metallicNodes.length; i ++ ) {
 
-			var metallicData = [];
+					var metallicNode = metallicNodes[ i ];
+					metallicData.push( {
+						name: metallicNode.getAttribute( 'name' ),
+						// required
+						metallicness: parseFloat( metallicNode.getAttribute( 'metallicness' ) ),
+						// required
+						roughness: parseFloat( metallicNode.getAttribute( 'roughness' ) ) // required
 
-			for ( var i = 0; i < metallicNodes.length; i ++ ) {
+					} );
 
-				var metallicNode = metallicNodes[ i ];
+				}
 
-				metallicData.push( {
-					name: metallicNode.getAttribute( 'name' ), // required
-					metallicness: parseFloat( metallicNode.getAttribute( 'metallicness' ) ), // required
-					roughness: parseFloat( metallicNode.getAttribute( 'roughness' ) ) // required
-				} );
+				metallicDisplaypropertiesData.data = metallicData;
+				return metallicDisplaypropertiesData;
 
 			}
 
-			metallicDisplaypropertiesData.data = metallicData;
+			function parseBasematerialNode( basematerialNode ) {
 
-			return metallicDisplaypropertiesData;
+				var basematerialData = {};
+				basematerialData[ 'name' ] = basematerialNode.getAttribute( 'name' ); // required
 
-		}
+				basematerialData[ 'displaycolor' ] = basematerialNode.getAttribute( 'displaycolor' ); // required
 
-		function parseBasematerialNode( basematerialNode ) {
+				basematerialData[ 'displaypropertiesid' ] = basematerialNode.getAttribute( 'displaypropertiesid' );
+				return basematerialData;
 
-			var basematerialData = {};
+			}
 
-			basematerialData[ 'name' ] = basematerialNode.getAttribute( 'name' ); // required
-			basematerialData[ 'displaycolor' ] = basematerialNode.getAttribute( 'displaycolor' ); // required
-			basematerialData[ 'displaypropertiesid' ] = basematerialNode.getAttribute( 'displaypropertiesid' );
+			function parseMeshNode( meshNode ) {
 
-			return basematerialData;
+				var meshData = {};
+				var vertices = [];
+				var vertexNodes = meshNode.querySelectorAll( 'vertices vertex' );
 
-		}
+				for ( var i = 0; i < vertexNodes.length; i ++ ) {
 
-		function parseMeshNode( meshNode ) {
+					var vertexNode = vertexNodes[ i ];
+					var x = vertexNode.getAttribute( 'x' );
+					var y = vertexNode.getAttribute( 'y' );
+					var z = vertexNode.getAttribute( 'z' );
+					vertices.push( parseFloat( x ), parseFloat( y ), parseFloat( z ) );
 
-			var meshData = {};
+				}
 
-			var vertices = [];
-			var vertexNodes = meshNode.querySelectorAll( 'vertices vertex' );
+				meshData[ 'vertices' ] = new Float32Array( vertices );
+				var triangleProperties = [];
+				var triangles = [];
+				var triangleNodes = meshNode.querySelectorAll( 'triangles triangle' );
 
-			for ( var i = 0; i < vertexNodes.length; i ++ ) {
+				for ( var i = 0; i < triangleNodes.length; i ++ ) {
 
-				var vertexNode = vertexNodes[ i ];
-				var x = vertexNode.getAttribute( 'x' );
-				var y = vertexNode.getAttribute( 'y' );
-				var z = vertexNode.getAttribute( 'z' );
+					var triangleNode = triangleNodes[ i ];
+					var v1 = triangleNode.getAttribute( 'v1' );
+					var v2 = triangleNode.getAttribute( 'v2' );
+					var v3 = triangleNode.getAttribute( 'v3' );
+					var p1 = triangleNode.getAttribute( 'p1' );
+					var p2 = triangleNode.getAttribute( 'p2' );
+					var p3 = triangleNode.getAttribute( 'p3' );
+					var pid = triangleNode.getAttribute( 'pid' );
+					var triangleProperty = {};
+					triangleProperty[ 'v1' ] = parseInt( v1, 10 );
+					triangleProperty[ 'v2' ] = parseInt( v2, 10 );
+					triangleProperty[ 'v3' ] = parseInt( v3, 10 );
+					triangles.push( triangleProperty[ 'v1' ], triangleProperty[ 'v2' ], triangleProperty[ 'v3' ] ); // optional
 
-				vertices.push( parseFloat( x ), parseFloat( y ), parseFloat( z ) );
+					if ( p1 ) {
 
-			}
+						triangleProperty[ 'p1' ] = parseInt( p1, 10 );
 
-			meshData[ 'vertices' ] = new Float32Array( vertices );
+					}
 
-			var triangleProperties = [];
-			var triangles = [];
-			var triangleNodes = meshNode.querySelectorAll( 'triangles triangle' );
+					if ( p2 ) {
 
-			for ( var i = 0; i < triangleNodes.length; i ++ ) {
+						triangleProperty[ 'p2' ] = parseInt( p2, 10 );
 
-				var triangleNode = triangleNodes[ i ];
-				var v1 = triangleNode.getAttribute( 'v1' );
-				var v2 = triangleNode.getAttribute( 'v2' );
-				var v3 = triangleNode.getAttribute( 'v3' );
-				var p1 = triangleNode.getAttribute( 'p1' );
-				var p2 = triangleNode.getAttribute( 'p2' );
-				var p3 = triangleNode.getAttribute( 'p3' );
-				var pid = triangleNode.getAttribute( 'pid' );
+					}
 
-				var triangleProperty = {};
+					if ( p3 ) {
 
-				triangleProperty[ 'v1' ] = parseInt( v1, 10 );
-				triangleProperty[ 'v2' ] = parseInt( v2, 10 );
-				triangleProperty[ 'v3' ] = parseInt( v3, 10 );
+						triangleProperty[ 'p3' ] = parseInt( p3, 10 );
 
-				triangles.push( triangleProperty[ 'v1' ], triangleProperty[ 'v2' ], triangleProperty[ 'v3' ] );
+					}
 
-				// optional
+					if ( pid ) {
 
-				if ( p1 ) {
+						triangleProperty[ 'pid' ] = pid;
 
-					triangleProperty[ 'p1' ] = parseInt( p1, 10 );
+					}
 
-				}
+					if ( 0 < Object.keys( triangleProperty ).length ) {
 
-				if ( p2 ) {
+						triangleProperties.push( triangleProperty );
 
-					triangleProperty[ 'p2' ] = parseInt( p2, 10 );
+					}
 
 				}
 
-				if ( p3 ) {
-
-					triangleProperty[ 'p3' ] = parseInt( p3, 10 );
-
-				}
+				meshData[ 'triangleProperties' ] = triangleProperties;
+				meshData[ 'triangles' ] = new Uint32Array( triangles );
+				return meshData;
 
-				if ( pid ) {
+			}
 
-					triangleProperty[ 'pid' ] = pid;
+			function parseComponentsNode( componentsNode ) {
 
-				}
+				var components = [];
+				var componentNodes = componentsNode.querySelectorAll( 'component' );
 
-				if ( 0 < Object.keys( triangleProperty ).length ) {
+				for ( var i = 0; i < componentNodes.length; i ++ ) {
 
-					triangleProperties.push( triangleProperty );
+					var componentNode = componentNodes[ i ];
+					var componentData = parseComponentNode( componentNode );
+					components.push( componentData );
 
 				}
 
-			}
+				return components;
 
-			meshData[ 'triangleProperties' ] = triangleProperties;
-			meshData[ 'triangles' ] = new Uint32Array( triangles );
+			}
 
-			return meshData;
+			function parseComponentNode( componentNode ) {
 
-		}
+				var componentData = {};
+				componentData[ 'objectId' ] = componentNode.getAttribute( 'objectid' ); // required
 
-		function parseComponentsNode( componentsNode ) {
+				var transform = componentNode.getAttribute( 'transform' );
 
-			var components = [];
+				if ( transform ) {
 
-			var componentNodes = componentsNode.querySelectorAll( 'component' );
+					componentData[ 'transform' ] = parseTransform( transform );
 
-			for ( var i = 0; i < componentNodes.length; i ++ ) {
+				}
 
-				var componentNode = componentNodes[ i ];
-				var componentData = parseComponentNode( componentNode );
-				components.push( componentData );
+				return componentData;
 
 			}
 
-			return components;
-
-		}
-
-		function parseComponentNode( componentNode ) {
-
-			var componentData = {};
-
-			componentData[ 'objectId' ] = componentNode.getAttribute( 'objectid' ); // required
+			function parseTransform( transform ) {
 
-			var transform = componentNode.getAttribute( 'transform' );
+				var t = [];
+				transform.split( ' ' ).forEach( function ( s ) {
 
-			if ( transform ) {
+					t.push( parseFloat( s ) );
 
-				componentData[ 'transform' ] = parseTransform( transform );
+				} );
+				var matrix = new THREE.Matrix4();
+				matrix.set( t[ 0 ], t[ 3 ], t[ 6 ], t[ 9 ], t[ 1 ], t[ 4 ], t[ 7 ], t[ 10 ], t[ 2 ], t[ 5 ], t[ 8 ], t[ 11 ], 0.0, 0.0, 0.0, 1.0 );
+				return matrix;
 
 			}
 
-			return componentData;
+			function parseObjectNode( objectNode ) {
 
-		}
+				var objectData = {
+					type: objectNode.getAttribute( 'type' )
+				};
+				var id = objectNode.getAttribute( 'id' );
 
-		function parseTransform( transform ) {
+				if ( id ) {
 
-			var t = [];
-			transform.split( ' ' ).forEach( function ( s ) {
+					objectData[ 'id' ] = id;
 
-				t.push( parseFloat( s ) );
+				}
 
-			} );
+				var pid = objectNode.getAttribute( 'pid' );
 
-			var matrix = new THREE.Matrix4();
-			matrix.set(
-				t[ 0 ], t[ 3 ], t[ 6 ], t[ 9 ],
-				t[ 1 ], t[ 4 ], t[ 7 ], t[ 10 ],
-				t[ 2 ], t[ 5 ], t[ 8 ], t[ 11 ],
-				 0.0, 0.0, 0.0, 1.0
-			);
+				if ( pid ) {
 
-			return matrix;
+					objectData[ 'pid' ] = pid;
 
-		}
+				}
 
-		function parseObjectNode( objectNode ) {
+				var pindex = objectNode.getAttribute( 'pindex' );
 
-			var objectData = {
-				type: objectNode.getAttribute( 'type' )
-			};
+				if ( pindex ) {
 
-			var id = objectNode.getAttribute( 'id' );
+					objectData[ 'pindex' ] = pindex;
 
-			if ( id ) {
+				}
 
-				objectData[ 'id' ] = id;
+				var thumbnail = objectNode.getAttribute( 'thumbnail' );
 
-			}
+				if ( thumbnail ) {
 
-			var pid = objectNode.getAttribute( 'pid' );
+					objectData[ 'thumbnail' ] = thumbnail;
 
-			if ( pid ) {
+				}
 
-				objectData[ 'pid' ] = pid;
+				var partnumber = objectNode.getAttribute( 'partnumber' );
 
-			}
+				if ( partnumber ) {
 
-			var pindex = objectNode.getAttribute( 'pindex' );
+					objectData[ 'partnumber' ] = partnumber;
 
-			if ( pindex ) {
+				}
 
-				objectData[ 'pindex' ] = pindex;
+				var name = objectNode.getAttribute( 'name' );
 
-			}
+				if ( name ) {
 
-			var thumbnail = objectNode.getAttribute( 'thumbnail' );
+					objectData[ 'name' ] = name;
 
-			if ( thumbnail ) {
+				}
 
-				objectData[ 'thumbnail' ] = thumbnail;
+				var meshNode = objectNode.querySelector( 'mesh' );
 
-			}
+				if ( meshNode ) {
 
-			var partnumber = objectNode.getAttribute( 'partnumber' );
+					objectData[ 'mesh' ] = parseMeshNode( meshNode );
 
-			if ( partnumber ) {
+				}
 
-				objectData[ 'partnumber' ] = partnumber;
+				var componentsNode = objectNode.querySelector( 'components' );
 
-			}
+				if ( componentsNode ) {
 
-			var name = objectNode.getAttribute( 'name' );
+					objectData[ 'components' ] = parseComponentsNode( componentsNode );
 
-			if ( name ) {
+				}
 
-				objectData[ 'name' ] = name;
+				return objectData;
 
 			}
 
-			var meshNode = objectNode.querySelector( 'mesh' );
+			function parseResourcesNode( resourcesNode ) {
 
-			if ( meshNode ) {
+				var resourcesData = {};
+				resourcesData[ 'basematerials' ] = {};
+				var basematerialsNodes = resourcesNode.querySelectorAll( 'basematerials' );
 
-				objectData[ 'mesh' ] = parseMeshNode( meshNode );
+				for ( var i = 0; i < basematerialsNodes.length; i ++ ) {
 
-			}
-
-			var componentsNode = objectNode.querySelector( 'components' );
+					var basematerialsNode = basematerialsNodes[ i ];
+					var basematerialsData = parseBasematerialsNode( basematerialsNode );
+					resourcesData[ 'basematerials' ][ basematerialsData[ 'id' ] ] = basematerialsData;
 
-			if ( componentsNode ) {
+				} //
 
-				objectData[ 'components' ] = parseComponentsNode( componentsNode );
 
-			}
+				resourcesData[ 'texture2d' ] = {};
+				var textures2DNodes = resourcesNode.querySelectorAll( 'texture2d' );
 
-			return objectData;
-
-		}
+				for ( var i = 0; i < textures2DNodes.length; i ++ ) {
 
-		function parseResourcesNode( resourcesNode ) {
+					var textures2DNode = textures2DNodes[ i ];
+					var texture2DData = parseTexture2DNode( textures2DNode );
+					resourcesData[ 'texture2d' ][ texture2DData[ 'id' ] ] = texture2DData;
 
-			var resourcesData = {};
+				} //
 
-			resourcesData[ 'basematerials' ] = {};
-			var basematerialsNodes = resourcesNode.querySelectorAll( 'basematerials' );
 
-			for ( var i = 0; i < basematerialsNodes.length; i ++ ) {
+				resourcesData[ 'colorgroup' ] = {};
+				var colorGroupNodes = resourcesNode.querySelectorAll( 'colorgroup' );
 
-				var basematerialsNode = basematerialsNodes[ i ];
-				var basematerialsData = parseBasematerialsNode( basematerialsNode );
-				resourcesData[ 'basematerials' ][ basematerialsData[ 'id' ] ] = basematerialsData;
+				for ( var i = 0; i < colorGroupNodes.length; i ++ ) {
 
-			}
+					var colorGroupNode = colorGroupNodes[ i ];
+					var colorGroupData = parseColorGroupNode( colorGroupNode );
+					resourcesData[ 'colorgroup' ][ colorGroupData[ 'id' ] ] = colorGroupData;
 
-			//
+				} //
 
-			resourcesData[ 'texture2d' ] = {};
-			var textures2DNodes = resourcesNode.querySelectorAll( 'texture2d' );
 
-			for ( var i = 0; i < textures2DNodes.length; i ++ ) {
+				resourcesData[ 'pbmetallicdisplayproperties' ] = {};
+				var pbmetallicdisplaypropertiesNodes = resourcesNode.querySelectorAll( 'pbmetallicdisplayproperties' );
 
-				var textures2DNode = textures2DNodes[ i ];
-				var texture2DData = parseTexture2DNode( textures2DNode );
-				resourcesData[ 'texture2d' ][ texture2DData[ 'id' ] ] = texture2DData;
+				for ( var i = 0; i < pbmetallicdisplaypropertiesNodes.length; i ++ ) {
 
-			}
+					var pbmetallicdisplaypropertiesNode = pbmetallicdisplaypropertiesNodes[ i ];
+					var pbmetallicdisplaypropertiesData = parseMetallicDisplaypropertiesNode( pbmetallicdisplaypropertiesNode );
+					resourcesData[ 'pbmetallicdisplayproperties' ][ pbmetallicdisplaypropertiesData[ 'id' ] ] = pbmetallicdisplaypropertiesData;
 
-			//
+				} //
 
-			resourcesData[ 'colorgroup' ] = {};
-			var colorGroupNodes = resourcesNode.querySelectorAll( 'colorgroup' );
 
-			for ( var i = 0; i < colorGroupNodes.length; i ++ ) {
+				resourcesData[ 'texture2dgroup' ] = {};
+				var textures2DGroupNodes = resourcesNode.querySelectorAll( 'texture2dgroup' );
 
-				var colorGroupNode = colorGroupNodes[ i ];
-				var colorGroupData = parseColorGroupNode( colorGroupNode );
-				resourcesData[ 'colorgroup' ][ colorGroupData[ 'id' ] ] = colorGroupData;
+				for ( var i = 0; i < textures2DGroupNodes.length; i ++ ) {
 
-			}
+					var textures2DGroupNode = textures2DGroupNodes[ i ];
+					var textures2DGroupData = parseTextures2DGroupNode( textures2DGroupNode );
+					resourcesData[ 'texture2dgroup' ][ textures2DGroupData[ 'id' ] ] = textures2DGroupData;
 
-			//
+				} //
 
-			resourcesData[ 'pbmetallicdisplayproperties' ] = {};
-			var pbmetallicdisplaypropertiesNodes = resourcesNode.querySelectorAll( 'pbmetallicdisplayproperties' );
 
-			for ( var i = 0; i < pbmetallicdisplaypropertiesNodes.length; i ++ ) {
+				resourcesData[ 'object' ] = {};
+				var objectNodes = resourcesNode.querySelectorAll( 'object' );
 
-				var pbmetallicdisplaypropertiesNode = pbmetallicdisplaypropertiesNodes[ i ];
-				var pbmetallicdisplaypropertiesData = parseMetallicDisplaypropertiesNode( pbmetallicdisplaypropertiesNode );
-				resourcesData[ 'pbmetallicdisplayproperties' ][ pbmetallicdisplaypropertiesData[ 'id' ] ] = pbmetallicdisplaypropertiesData;
+				for ( var i = 0; i < objectNodes.length; i ++ ) {
 
-			}
+					var objectNode = objectNodes[ i ];
+					var objectData = parseObjectNode( objectNode );
+					resourcesData[ 'object' ][ objectData[ 'id' ] ] = objectData;
 
-			//
+				}
 
-			resourcesData[ 'texture2dgroup' ] = {};
-			var textures2DGroupNodes = resourcesNode.querySelectorAll( 'texture2dgroup' );
+				return resourcesData;
 
-			for ( var i = 0; i < textures2DGroupNodes.length; i ++ ) {
+			}
 
-				var textures2DGroupNode = textures2DGroupNodes[ i ];
-				var textures2DGroupData = parseTextures2DGroupNode( textures2DGroupNode );
-				resourcesData[ 'texture2dgroup' ][ textures2DGroupData[ 'id' ] ] = textures2DGroupData;
+			function parseBuildNode( buildNode ) {
 
-			}
+				var buildData = [];
+				var itemNodes = buildNode.querySelectorAll( 'item' );
 
-			//
+				for ( var i = 0; i < itemNodes.length; i ++ ) {
 
-			resourcesData[ 'object' ] = {};
-			var objectNodes = resourcesNode.querySelectorAll( 'object' );
+					var itemNode = itemNodes[ i ];
+					var buildItem = {
+						objectId: itemNode.getAttribute( 'objectid' )
+					};
+					var transform = itemNode.getAttribute( 'transform' );
 
-			for ( var i = 0; i < objectNodes.length; i ++ ) {
+					if ( transform ) {
 
-				var objectNode = objectNodes[ i ];
-				var objectData = parseObjectNode( objectNode );
-				resourcesData[ 'object' ][ objectData[ 'id' ] ] = objectData;
+						buildItem[ 'transform' ] = parseTransform( transform );
 
-			}
+					}
 
-			return resourcesData;
+					buildData.push( buildItem );
 
-		}
+				}
 
-		function parseBuildNode( buildNode ) {
+				return buildData;
 
-			var buildData = [];
-			var itemNodes = buildNode.querySelectorAll( 'item' );
+			}
 
-			for ( var i = 0; i < itemNodes.length; i ++ ) {
+			function parseModelNode( modelNode ) {
 
-				var itemNode = itemNodes[ i ];
-				var buildItem = {
-					objectId: itemNode.getAttribute( 'objectid' )
+				var modelData = {
+					unit: modelNode.getAttribute( 'unit' ) || 'millimeter'
 				};
-				var transform = itemNode.getAttribute( 'transform' );
+				var metadataNodes = modelNode.querySelectorAll( 'metadata' );
 
-				if ( transform ) {
+				if ( metadataNodes ) {
 
-					buildItem[ 'transform' ] = parseTransform( transform );
+					modelData[ 'metadata' ] = parseMetadataNodes( metadataNodes );
 
 				}
 
-				buildData.push( buildItem );
+				var resourcesNode = modelNode.querySelector( 'resources' );
 
-			}
+				if ( resourcesNode ) {
 
-			return buildData;
+					modelData[ 'resources' ] = parseResourcesNode( resourcesNode );
 
-		}
-
-		function parseModelNode( modelNode ) {
-
-			var modelData = { unit: modelNode.getAttribute( 'unit' ) || 'millimeter' };
-			var metadataNodes = modelNode.querySelectorAll( 'metadata' );
+				}
 
-			if ( metadataNodes ) {
+				var buildNode = modelNode.querySelector( 'build' );
 
-				modelData[ 'metadata' ] = parseMetadataNodes( metadataNodes );
+				if ( buildNode ) {
 
-			}
+					modelData[ 'build' ] = parseBuildNode( buildNode );
 
-			var resourcesNode = modelNode.querySelector( 'resources' );
-
-			if ( resourcesNode ) {
+				}
 
-				modelData[ 'resources' ] = parseResourcesNode( resourcesNode );
+				return modelData;
 
 			}
 
-			var buildNode = modelNode.querySelector( 'build' );
+			function buildTexture( texture2dgroup, objects, modelData, textureData ) {
 
-			if ( buildNode ) {
+				var texid = texture2dgroup.texid;
+				var texture2ds = modelData.resources.texture2d;
+				var texture2d = texture2ds[ texid ];
 
-				modelData[ 'build' ] = parseBuildNode( buildNode );
+				if ( texture2d ) {
 
-			}
+					var data = textureData[ texture2d.path ];
+					var type = texture2d.contenttype;
+					var blob = new Blob( [ data ], {
+						type: type
+					} );
+					var sourceURI = URL.createObjectURL( blob );
+					var texture = textureLoader.load( sourceURI, function () {
 
-			return modelData;
+						URL.revokeObjectURL( sourceURI );
 
-		}
+					} );
+					texture.encoding = THREE.sRGBEncoding; // texture parameters
 
-		function buildTexture( texture2dgroup, objects, modelData, textureData ) {
+					switch ( texture2d.tilestyleu ) {
 
-			var texid = texture2dgroup.texid;
-			var texture2ds = modelData.resources.texture2d;
-			var texture2d = texture2ds[ texid ];
+						case 'wrap':
+							texture.wrapS = THREE.RepeatWrapping;
+							break;
 
-			if ( texture2d ) {
+						case 'mirror':
+							texture.wrapS = THREE.MirroredRepeatWrapping;
+							break;
 
-				var data = textureData[ texture2d.path ];
-				var type = texture2d.contenttype;
+						case 'none':
+						case 'clamp':
+							texture.wrapS = THREE.ClampToEdgeWrapping;
+							break;
 
-				var blob = new Blob( [ data ], { type: type } );
-				var sourceURI = URL.createObjectURL( blob );
+						default:
+							texture.wrapS = THREE.RepeatWrapping;
 
-				var texture = textureLoader.load( sourceURI, function () {
+					}
 
-					URL.revokeObjectURL( sourceURI );
+					switch ( texture2d.tilestylev ) {
 
-				} );
+						case 'wrap':
+							texture.wrapT = THREE.RepeatWrapping;
+							break;
 
-				texture.encoding = THREE.sRGBEncoding;
+						case 'mirror':
+							texture.wrapT = THREE.MirroredRepeatWrapping;
+							break;
 
-				// texture parameters
+						case 'none':
+						case 'clamp':
+							texture.wrapT = THREE.ClampToEdgeWrapping;
+							break;
 
-				switch ( texture2d.tilestyleu ) {
+						default:
+							texture.wrapT = THREE.RepeatWrapping;
 
-					case 'wrap':
-						texture.wrapS = THREE.RepeatWrapping;
-						break;
+					}
 
-					case 'mirror':
-						texture.wrapS = THREE.MirroredRepeatWrapping;
-						break;
+					switch ( texture2d.filter ) {
 
-					case 'none':
-					case 'clamp':
-						texture.wrapS = THREE.ClampToEdgeWrapping;
-						break;
+						case 'auto':
+							texture.magFilter = THREE.LinearFilter;
+							texture.minFilter = THREE.LinearMipmapLinearFilter;
+							break;
 
-					default:
-						texture.wrapS = THREE.RepeatWrapping;
+						case 'linear':
+							texture.magFilter = THREE.LinearFilter;
+							texture.minFilter = THREE.LinearFilter;
+							break;
 
-				}
+						case 'nearest':
+							texture.magFilter = THREE.NearestFilter;
+							texture.minFilter = THREE.NearestFilter;
+							break;
 
-				switch ( texture2d.tilestylev ) {
+						default:
+							texture.magFilter = THREE.LinearFilter;
+							texture.minFilter = THREE.LinearMipmapLinearFilter;
 
-					case 'wrap':
-						texture.wrapT = THREE.RepeatWrapping;
-						break;
+					}
 
-					case 'mirror':
-						texture.wrapT = THREE.MirroredRepeatWrapping;
-						break;
+					return texture;
 
-					case 'none':
-					case 'clamp':
-						texture.wrapT = THREE.ClampToEdgeWrapping;
-						break;
+				} else {
 
-					default:
-						texture.wrapT = THREE.RepeatWrapping;
+					return null;
 
 				}
 
-				switch ( texture2d.filter ) {
+			}
 
-					case 'auto':
-						texture.magFilter = THREE.LinearFilter;
-						texture.minFilter = THREE.LinearMipmapLinearFilter;
-						break;
+			function buildBasematerialsMeshes( basematerials, triangleProperties, modelData, meshData, textureData, objectData ) {
 
-					case 'linear':
-						texture.magFilter = THREE.LinearFilter;
-						texture.minFilter = THREE.LinearFilter;
-						break;
+				var objectPindex = objectData.pindex;
+				var materialMap = {};
 
-					case 'nearest':
-						texture.magFilter = THREE.NearestFilter;
-						texture.minFilter = THREE.NearestFilter;
-						break;
+				for ( var i = 0, l = triangleProperties.length; i < l; i ++ ) {
 
-					default:
-						texture.magFilter = THREE.LinearFilter;
-						texture.minFilter = THREE.LinearMipmapLinearFilter;
+					var triangleProperty = triangleProperties[ i ];
+					var pindex = triangleProperty.p1 !== undefined ? triangleProperty.p1 : objectPindex;
+					if ( materialMap[ pindex ] === undefined ) materialMap[ pindex ] = [];
+					materialMap[ pindex ].push( triangleProperty );
 
-				}
+				} //
 
-				return texture;
 
-			} else {
+				var keys = Object.keys( materialMap );
+				var meshes = [];
 
-				return null;
+				for ( var i = 0, l = keys.length; i < l; i ++ ) {
 
-			}
+					var materialIndex = keys[ i ];
+					var trianglePropertiesProps = materialMap[ materialIndex ];
+					var basematerialData = basematerials.basematerials[ materialIndex ];
+					var material = getBuild( basematerialData, objects, modelData, textureData, objectData, buildBasematerial ); //
 
-		}
+					var geometry = new THREE.BufferGeometry();
+					var positionData = [];
+					var vertices = meshData.vertices;
 
-		function buildBasematerialsMeshes( basematerials, triangleProperties, modelData, meshData, textureData, objectData ) {
+					for ( var j = 0, jl = trianglePropertiesProps.length; j < jl; j ++ ) {
 
-			var objectPindex = objectData.pindex;
+						var triangleProperty = trianglePropertiesProps[ j ];
+						positionData.push( vertices[ triangleProperty.v1 * 3 + 0 ] );
+						positionData.push( vertices[ triangleProperty.v1 * 3 + 1 ] );
+						positionData.push( vertices[ triangleProperty.v1 * 3 + 2 ] );
+						positionData.push( vertices[ triangleProperty.v2 * 3 + 0 ] );
+						positionData.push( vertices[ triangleProperty.v2 * 3 + 1 ] );
+						positionData.push( vertices[ triangleProperty.v2 * 3 + 2 ] );
+						positionData.push( vertices[ triangleProperty.v3 * 3 + 0 ] );
+						positionData.push( vertices[ triangleProperty.v3 * 3 + 1 ] );
+						positionData.push( vertices[ triangleProperty.v3 * 3 + 2 ] );
 
-			var materialMap = {};
+					}
 
-			for ( var i = 0, l = triangleProperties.length; i < l; i ++ ) {
+					geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positionData, 3 ) ); //
 
-				var triangleProperty = triangleProperties[ i ];
-				var pindex = ( triangleProperty.p1 !== undefined ) ? triangleProperty.p1 : objectPindex;
+					var mesh = new THREE.Mesh( geometry, material );
+					meshes.push( mesh );
 
-				if ( materialMap[ pindex ] === undefined ) materialMap[ pindex ] = [];
+				}
 
-				materialMap[ pindex ].push( triangleProperty );
+				return meshes;
 
 			}
 
-			//
-
-			var keys = Object.keys( materialMap );
-			var meshes = [];
-
-			for ( var i = 0, l = keys.length; i < l; i ++ ) {
-
-				var materialIndex = keys[ i ];
-				var trianglePropertiesProps = materialMap[ materialIndex ];
-				var basematerialData = basematerials.basematerials[ materialIndex ];
-				var material = getBuild( basematerialData, objects, modelData, textureData, objectData, buildBasematerial );
-
-				//
+			function buildTexturedMesh( texture2dgroup, triangleProperties, modelData, meshData, textureData, objectData ) {
 
+				// geometry
 				var geometry = new THREE.BufferGeometry();
-
 				var positionData = [];
-
+				var uvData = [];
 				var vertices = meshData.vertices;
-
-				for ( var j = 0, jl = trianglePropertiesProps.length; j < jl; j ++ ) {
-
-					var triangleProperty = trianglePropertiesProps[ j ];
-
-					positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 0 ] );
-					positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 1 ] );
-					positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 2 ] );
-
-					positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 0 ] );
-					positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 1 ] );
-					positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 2 ] );
-
-					positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 0 ] );
-					positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 1 ] );
-					positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 2 ] );
-
+				var uvs = texture2dgroup.uvs;
+
+				for ( var i = 0, l = triangleProperties.length; i < l; i ++ ) {
+
+					var triangleProperty = triangleProperties[ i ];
+					positionData.push( vertices[ triangleProperty.v1 * 3 + 0 ] );
+					positionData.push( vertices[ triangleProperty.v1 * 3 + 1 ] );
+					positionData.push( vertices[ triangleProperty.v1 * 3 + 2 ] );
+					positionData.push( vertices[ triangleProperty.v2 * 3 + 0 ] );
+					positionData.push( vertices[ triangleProperty.v2 * 3 + 1 ] );
+					positionData.push( vertices[ triangleProperty.v2 * 3 + 2 ] );
+					positionData.push( vertices[ triangleProperty.v3 * 3 + 0 ] );
+					positionData.push( vertices[ triangleProperty.v3 * 3 + 1 ] );
+					positionData.push( vertices[ triangleProperty.v3 * 3 + 2 ] ); //
+
+					uvData.push( uvs[ triangleProperty.p1 * 2 + 0 ] );
+					uvData.push( uvs[ triangleProperty.p1 * 2 + 1 ] );
+					uvData.push( uvs[ triangleProperty.p2 * 2 + 0 ] );
+					uvData.push( uvs[ triangleProperty.p2 * 2 + 1 ] );
+					uvData.push( uvs[ triangleProperty.p3 * 2 + 0 ] );
+					uvData.push( uvs[ triangleProperty.p3 * 2 + 1 ] );
 
 				}
 
 				geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positionData, 3 ) );
+				geometry.setAttribute( 'uv', new THREE.Float32BufferAttribute( uvData, 2 ) ); // material
 
-				//
+				var texture = getBuild( texture2dgroup, objects, modelData, textureData, objectData, buildTexture );
+				var material = new THREE.MeshPhongMaterial( {
+					map: texture,
+					flatShading: true
+				} ); // mesh
 
 				var mesh = new THREE.Mesh( geometry, material );
-				meshes.push( mesh );
+				return mesh;
 
 			}
 
-			return meshes;
-
-		}
-
-		function buildTexturedMesh( texture2dgroup, triangleProperties, modelData, meshData, textureData, objectData ) {
+			function buildVertexColorMesh( colorgroup, triangleProperties, modelData, meshData, objectData ) {
 
-			// geometry
-
-			var geometry = new THREE.BufferGeometry();
-
-			var positionData = [];
-			var uvData = [];
-
-			var vertices = meshData.vertices;
-			var uvs = texture2dgroup.uvs;
-
-			for ( var i = 0, l = triangleProperties.length; i < l; i ++ ) {
-
-				var triangleProperty = triangleProperties[ i ];
-
-				positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 0 ] );
-				positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 1 ] );
-				positionData.push( vertices[ ( triangleProperty.v1 * 3 ) + 2 ] );
-
-				positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 0 ] );
-				positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 1 ] );
-				positionData.push( vertices[ ( triangleProperty.v2 * 3 ) + 2 ] );
-
-				positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 0 ] );
-				positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 1 ] );
-				positionData.push( vertices[ ( triangleProperty.v3 * 3 ) + 2 ] );
+				// geometry
+				var geometry = new THREE.BufferGeometry();
+				var positionData = [];
+				var colorData = [];
+				var vertices = meshData.vertices;
+				var colors = colorgroup.colors;
+
+				for ( var i = 0, l = triangleProperties.length; i < l; i ++ ) {
+
+					var triangleProperty = triangleProperties[ i ];
+					var v1 = triangleProperty.v1;
+					var v2 = triangleProperty.v2;
+					var v3 = triangleProperty.v3;
+					positionData.push( vertices[ v1 * 3 + 0 ] );
+					positionData.push( vertices[ v1 * 3 + 1 ] );
+					positionData.push( vertices[ v1 * 3 + 2 ] );
+					positionData.push( vertices[ v2 * 3 + 0 ] );
+					positionData.push( vertices[ v2 * 3 + 1 ] );
+					positionData.push( vertices[ v2 * 3 + 2 ] );
+					positionData.push( vertices[ v3 * 3 + 0 ] );
+					positionData.push( vertices[ v3 * 3 + 1 ] );
+					positionData.push( vertices[ v3 * 3 + 2 ] ); //
+
+					var p1 = triangleProperty.p1 !== undefined ? triangleProperty.p1 : objectData.pindex;
+					var p2 = triangleProperty.p2 !== undefined ? triangleProperty.p2 : p1;
+					var p3 = triangleProperty.p3 !== undefined ? triangleProperty.p3 : p1;
+					colorData.push( colors[ p1 * 3 + 0 ] );
+					colorData.push( colors[ p1 * 3 + 1 ] );
+					colorData.push( colors[ p1 * 3 + 2 ] );
+					colorData.push( colors[ p2 * 3 + 0 ] );
+					colorData.push( colors[ p2 * 3 + 1 ] );
+					colorData.push( colors[ p2 * 3 + 2 ] );
+					colorData.push( colors[ p3 * 3 + 0 ] );
+					colorData.push( colors[ p3 * 3 + 1 ] );
+					colorData.push( colors[ p3 * 3 + 2 ] );
 
-				//
+				}
 
-				uvData.push( uvs[ ( triangleProperty.p1 * 2 ) + 0 ] );
-				uvData.push( uvs[ ( triangleProperty.p1 * 2 ) + 1 ] );
+				geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positionData, 3 ) );
+				geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( colorData, 3 ) ); // material
 
-				uvData.push( uvs[ ( triangleProperty.p2 * 2 ) + 0 ] );
-				uvData.push( uvs[ ( triangleProperty.p2 * 2 ) + 1 ] );
+				var material = new THREE.MeshPhongMaterial( {
+					vertexColors: true,
+					flatShading: true
+				} ); // mesh
 
-				uvData.push( uvs[ ( triangleProperty.p3 * 2 ) + 0 ] );
-				uvData.push( uvs[ ( triangleProperty.p3 * 2 ) + 1 ] );
+				var mesh = new THREE.Mesh( geometry, material );
+				return mesh;
 
 			}
 
-			geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positionData, 3 ) );
-			geometry.setAttribute( 'uv', new THREE.Float32BufferAttribute( uvData, 2 ) );
-
-			// material
-
-			var texture = getBuild( texture2dgroup, objects, modelData, textureData, objectData, buildTexture );
-
-			var material = new THREE.MeshPhongMaterial( { map: texture, flatShading: true } );
-
-			// mesh
+			function buildDefaultMesh( meshData ) {
 
-			var mesh = new THREE.Mesh( geometry, material );
-
-			return mesh;
-
-		}
-
-		function buildVertexColorMesh( colorgroup, triangleProperties, modelData, meshData, objectData ) {
-
-			// geometry
-
-			var geometry = new THREE.BufferGeometry();
-
-			var positionData = [];
-			var colorData = [];
-
-			var vertices = meshData.vertices;
-			var colors = colorgroup.colors;
-
-			for ( var i = 0, l = triangleProperties.length; i < l; i ++ ) {
-
-				var triangleProperty = triangleProperties[ i ];
-
-				var v1 = triangleProperty.v1;
-				var v2 = triangleProperty.v2;
-				var v3 = triangleProperty.v3;
-
-				positionData.push( vertices[ ( v1 * 3 ) + 0 ] );
-				positionData.push( vertices[ ( v1 * 3 ) + 1 ] );
-				positionData.push( vertices[ ( v1 * 3 ) + 2 ] );
-
-				positionData.push( vertices[ ( v2 * 3 ) + 0 ] );
-				positionData.push( vertices[ ( v2 * 3 ) + 1 ] );
-				positionData.push( vertices[ ( v2 * 3 ) + 2 ] );
-
-				positionData.push( vertices[ ( v3 * 3 ) + 0 ] );
-				positionData.push( vertices[ ( v3 * 3 ) + 1 ] );
-				positionData.push( vertices[ ( v3 * 3 ) + 2 ] );
-
-				//
-
-				var p1 = ( triangleProperty.p1 !== undefined ) ? triangleProperty.p1 : objectData.pindex;
-				var p2 = ( triangleProperty.p2 !== undefined ) ? triangleProperty.p2 : p1;
-				var p3 = ( triangleProperty.p3 !== undefined ) ? triangleProperty.p3 : p1;
-
-				colorData.push( colors[ ( p1 * 3 ) + 0 ] );
-				colorData.push( colors[ ( p1 * 3 ) + 1 ] );
-				colorData.push( colors[ ( p1 * 3 ) + 2 ] );
-
-				colorData.push( colors[ ( p2 * 3 ) + 0 ] );
-				colorData.push( colors[ ( p2 * 3 ) + 1 ] );
-				colorData.push( colors[ ( p2 * 3 ) + 2 ] );
-
-				colorData.push( colors[ ( p3 * 3 ) + 0 ] );
-				colorData.push( colors[ ( p3 * 3 ) + 1 ] );
-				colorData.push( colors[ ( p3 * 3 ) + 2 ] );
+				var geometry = new THREE.BufferGeometry();
+				geometry.setIndex( new THREE.BufferAttribute( meshData[ 'triangles' ], 1 ) );
+				geometry.setAttribute( 'position', new THREE.BufferAttribute( meshData[ 'vertices' ], 3 ) );
+				var material = new THREE.MeshPhongMaterial( {
+					color: 0xaaaaff,
+					flatShading: true
+				} );
+				var mesh = new THREE.Mesh( geometry, material );
+				return mesh;
 
 			}
 
-			geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positionData, 3 ) );
-			geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( colorData, 3 ) );
-
-			// material
-
-			var material = new THREE.MeshPhongMaterial( { vertexColors: true, flatShading: true } );
-
-			// mesh
-
-			var mesh = new THREE.Mesh( geometry, material );
-
-			return mesh;
-
-		}
-
-		function buildDefaultMesh( meshData ) {
-
-			var geometry = new THREE.BufferGeometry();
-			geometry.setIndex( new THREE.BufferAttribute( meshData[ 'triangles' ], 1 ) );
-			geometry.setAttribute( 'position', new THREE.BufferAttribute( meshData[ 'vertices' ], 3 ) );
+			function buildMeshes( resourceMap, modelData, meshData, textureData, objectData ) {
 
-			var material = new THREE.MeshPhongMaterial( { color: 0xaaaaff, flatShading: true } );
+				var keys = Object.keys( resourceMap );
+				var meshes = [];
 
-			var mesh = new THREE.Mesh( geometry, material );
+				for ( var i = 0, il = keys.length; i < il; i ++ ) {
 
-			return mesh;
-
-		}
+					var resourceId = keys[ i ];
+					var triangleProperties = resourceMap[ resourceId ];
+					var resourceType = getResourceType( resourceId, modelData );
 
-		function buildMeshes( resourceMap, modelData, meshData, textureData, objectData ) {
+					switch ( resourceType ) {
 
-			var keys = Object.keys( resourceMap );
-			var meshes = [];
+						case 'material':
+							var basematerials = modelData.resources.basematerials[ resourceId ];
+							var newMeshes = buildBasematerialsMeshes( basematerials, triangleProperties, modelData, meshData, textureData, objectData );
 
-			for ( var i = 0, il = keys.length; i < il; i ++ ) {
+							for ( var j = 0, jl = newMeshes.length; j < jl; j ++ ) {
 
-				var resourceId = keys[ i ];
-				var triangleProperties = resourceMap[ resourceId ];
-				var resourceType = getResourceType( resourceId, modelData );
+								meshes.push( newMeshes[ j ] );
 
-				switch ( resourceType ) {
+							}
 
-					case 'material':
-						var basematerials = modelData.resources.basematerials[ resourceId ];
-						var newMeshes = buildBasematerialsMeshes( basematerials, triangleProperties, modelData, meshData, textureData, objectData );
-
-						for ( var j = 0, jl = newMeshes.length; j < jl; j ++ ) {
-
-							meshes.push( newMeshes[ j ] );
-
-						}
+							break;
 
-						break;
+						case 'texture':
+							var texture2dgroup = modelData.resources.texture2dgroup[ resourceId ];
+							meshes.push( buildTexturedMesh( texture2dgroup, triangleProperties, modelData, meshData, textureData, objectData ) );
+							break;
 
-					case 'texture':
-						var texture2dgroup = modelData.resources.texture2dgroup[ resourceId ];
-						meshes.push( buildTexturedMesh( texture2dgroup, triangleProperties, modelData, meshData, textureData, objectData ) );
-						break;
+						case 'vertexColors':
+							var colorgroup = modelData.resources.colorgroup[ resourceId ];
+							meshes.push( buildVertexColorMesh( colorgroup, triangleProperties, modelData, meshData, objectData ) );
+							break;
 
-					case 'vertexColors':
-						var colorgroup = modelData.resources.colorgroup[ resourceId ];
-						meshes.push( buildVertexColorMesh( colorgroup, triangleProperties, modelData, meshData, objectData ) );
-						break;
+						case 'default':
+							meshes.push( buildDefaultMesh( meshData ) );
+							break;
 
-					case 'default':
-						meshes.push( buildDefaultMesh( meshData ) );
-						break;
+						default:
+							console.error( 'THREE.3MFLoader: Unsupported resource type.' );
 
-					default:
-						console.error( 'THREE.3MFLoader: Unsupported resource type.' );
+					}
 
 				}
 
+				return meshes;
+
 			}
 
-			return meshes;
+			function getResourceType( pid, modelData ) {
 
-		}
+				if ( modelData.resources.texture2dgroup[ pid ] !== undefined ) {
 
-		function getResourceType( pid, modelData ) {
+					return 'texture';
 
-			if ( modelData.resources.texture2dgroup[ pid ] !== undefined ) {
+				} else if ( modelData.resources.basematerials[ pid ] !== undefined ) {
 
-				return 'texture';
+					return 'material';
 
-			} else if ( modelData.resources.basematerials[ pid ] !== undefined ) {
+				} else if ( modelData.resources.colorgroup[ pid ] !== undefined ) {
 
-				return 'material';
+					return 'vertexColors';
 
-			} else if ( modelData.resources.colorgroup[ pid ] !== undefined ) {
+				} else if ( pid === 'default' ) {
 
-				return 'vertexColors';
+					return 'default';
 
-			} else if ( pid === 'default' ) {
-
-				return 'default';
+				} else {
 
-			} else {
+					return undefined;
 
-				return undefined;
+				}
 
 			}
 
-		}
-
-		function analyzeObject( modelData, meshData, objectData ) {
-
-			var resourceMap = {};
-
-			var triangleProperties = meshData[ 'triangleProperties' ];
+			function analyzeObject( modelData, meshData, objectData ) {
 
-			var objectPid = objectData.pid;
+				var resourceMap = {};
+				var triangleProperties = meshData[ 'triangleProperties' ];
+				var objectPid = objectData.pid;
 
-			for ( var i = 0, l = triangleProperties.length; i < l; i ++ ) {
+				for ( var i = 0, l = triangleProperties.length; i < l; i ++ ) {
 
-				var triangleProperty = triangleProperties[ i ];
-				var pid = ( triangleProperty.pid !== undefined ) ? triangleProperty.pid : objectPid;
+					var triangleProperty = triangleProperties[ i ];
+					var pid = triangleProperty.pid !== undefined ? triangleProperty.pid : objectPid;
+					if ( pid === undefined ) pid = 'default';
+					if ( resourceMap[ pid ] === undefined ) resourceMap[ pid ] = [];
+					resourceMap[ pid ].push( triangleProperty );
 
-				if ( pid === undefined ) pid = 'default';
-
-				if ( resourceMap[ pid ] === undefined ) resourceMap[ pid ] = [];
+				}
 
-				resourceMap[ pid ].push( triangleProperty );
+				return resourceMap;
 
 			}
 
-			return resourceMap;
+			function buildGroup( meshData, objects, modelData, textureData, objectData ) {
 
-		}
+				var group = new THREE.Group();
+				var resourceMap = analyzeObject( modelData, meshData, objectData );
+				var meshes = buildMeshes( resourceMap, modelData, meshData, textureData, objectData );
 
-		function buildGroup( meshData, objects, modelData, textureData, objectData ) {
+				for ( var i = 0, l = meshes.length; i < l; i ++ ) {
 
-			var group = new THREE.Group();
+					group.add( meshes[ i ] );
 
-			var resourceMap = analyzeObject( modelData, meshData, objectData );
-			var meshes = buildMeshes( resourceMap, modelData, meshData, textureData, objectData );
-
-			for ( var i = 0, l = meshes.length; i < l; i ++ ) {
+				}
 
-				group.add( meshes[ i ] );
+				return group;
 
 			}
 
-			return group;
+			function applyExtensions( extensions, meshData, modelXml ) {
 
-		}
-
-		function applyExtensions( extensions, meshData, modelXml ) {
+				if ( ! extensions ) {
 
-			if ( ! extensions ) {
+					return;
 
-				return;
+				}
 
-			}
+				var availableExtensions = [];
+				var keys = Object.keys( extensions );
 
-			var availableExtensions = [];
-			var keys = Object.keys( extensions );
+				for ( var i = 0; i < keys.length; i ++ ) {
 
-			for ( var i = 0; i < keys.length; i ++ ) {
+					var ns = keys[ i ];
 
-				var ns = keys[ i ];
+					for ( var j = 0; j < scope.availableExtensions.length; j ++ ) {
 
-				for ( var j = 0; j < scope.availableExtensions.length; j ++ ) {
+						var extension = scope.availableExtensions[ j ];
 
-					var extension = scope.availableExtensions[ j ];
+						if ( extension.ns === ns ) {
 
-					if ( extension.ns === ns ) {
+							availableExtensions.push( extension );
 
-						availableExtensions.push( extension );
+						}
 
 					}
 
 				}
 
-			}
+				for ( var i = 0; i < availableExtensions.length; i ++ ) {
 
-			for ( var i = 0; i < availableExtensions.length; i ++ ) {
+					var extension = availableExtensions[ i ];
+					extension.apply( modelXml, extensions[ extension[ 'ns' ] ], meshData );
 
-				var extension = availableExtensions[ i ];
-				extension.apply( modelXml, extensions[ extension[ 'ns' ] ], meshData );
+				}
 
 			}
 
-		}
+			function getBuild( data, objects, modelData, textureData, objectData, builder ) {
 
-		function getBuild( data, objects, modelData, textureData, objectData, builder ) {
+				if ( data.build !== undefined ) return data.build;
+				data.build = builder( data, objects, modelData, textureData, objectData );
+				return data.build;
 
-			if ( data.build !== undefined ) return data.build;
+			}
 
-			data.build = builder( data, objects, modelData, textureData, objectData );
+			function buildBasematerial( materialData, objects, modelData ) {
 
-			return data.build;
+				var material;
+				var displaypropertiesid = materialData.displaypropertiesid;
+				var pbmetallicdisplayproperties = modelData.resources.pbmetallicdisplayproperties;
 
-		}
+				if ( displaypropertiesid !== null && pbmetallicdisplayproperties[ displaypropertiesid ] !== undefined ) {
 
-		function buildBasematerial( materialData, objects, modelData ) {
+					// metallic display property, use StandardMaterial
+					var pbmetallicdisplayproperty = pbmetallicdisplayproperties[ displaypropertiesid ];
+					var metallicData = pbmetallicdisplayproperty.data[ materialData.index ];
+					material = new THREE.MeshStandardMaterial( {
+						flatShading: true,
+						roughness: metallicData.roughness,
+						metalness: metallicData.metallicness
+					} );
 
-			var material;
+				} else {
 
-			var displaypropertiesid = materialData.displaypropertiesid;
-			var pbmetallicdisplayproperties = modelData.resources.pbmetallicdisplayproperties;
+					// otherwise use PhongMaterial
+					material = new THREE.MeshPhongMaterial( {
+						flatShading: true
+					} );
 
-			if ( displaypropertiesid !== null && pbmetallicdisplayproperties[ displaypropertiesid ] !== undefined ) {
+				}
 
-				// metallic display property, use StandardMaterial
+				material.name = materialData.name; // displaycolor MUST be specified with a value of a 6 or 8 digit hexadecimal number, e.g. "#RRGGBB" or "#RRGGBBAA"
 
-				var pbmetallicdisplayproperty = pbmetallicdisplayproperties[ displaypropertiesid ];
-				var metallicData = pbmetallicdisplayproperty.data[ materialData.index ];
+				var displaycolor = materialData.displaycolor;
+				var color = displaycolor.substring( 0, 7 );
+				material.color.setStyle( color );
+				material.color.convertSRGBToLinear(); // displaycolor is in sRGB
+				// process alpha if set
 
-				material = new THREE.MeshStandardMaterial( { flatShading: true, roughness: metallicData.roughness, metalness: metallicData.metallicness } );
+				if ( displaycolor.length === 9 ) {
 
-			} else {
+					material.opacity = parseInt( displaycolor.charAt( 7 ) + displaycolor.charAt( 8 ), 16 ) / 255;
 
-				// otherwise use PhongMaterial
+				}
 
-				material = new THREE.MeshPhongMaterial( { flatShading: true } );
+				return material;
 
 			}
 
-			material.name = materialData.name;
+			function buildComposite( compositeData, objects, modelData, textureData ) {
 
-			// displaycolor MUST be specified with a value of a 6 or 8 digit hexadecimal number, e.g. "#RRGGBB" or "#RRGGBBAA"
+				var composite = new THREE.Group();
 
-			var displaycolor = materialData.displaycolor;
-
-			var color = displaycolor.substring( 0, 7 );
-			material.color.setStyle( color );
-			material.color.convertSRGBToLinear(); // displaycolor is in sRGB
-
-			// process alpha if set
-
-			if ( displaycolor.length === 9 ) {
-
-				material.opacity = parseInt( displaycolor.charAt( 7 ) + displaycolor.charAt( 8 ), 16 ) / 255;
-
-			}
-
-			return material;
-
-		}
+				for ( var j = 0; j < compositeData.length; j ++ ) {
 
-		function buildComposite( compositeData, objects, modelData, textureData ) {
+					var component = compositeData[ j ];
+					var build = objects[ component.objectId ];
 
-			var composite = new THREE.Group();
+					if ( build === undefined ) {
 
-			for ( var j = 0; j < compositeData.length; j ++ ) {
+						buildObject( component.objectId, objects, modelData, textureData );
+						build = objects[ component.objectId ];
 
-				var component = compositeData[ j ];
-				var build = objects[ component.objectId ];
-
-				if ( build === undefined ) {
+					}
 
-					buildObject( component.objectId, objects, modelData, textureData );
-					build = objects[ component.objectId ];
+					var object3D = build.clone(); // apply component transform
 
-				}
+					var transform = component.transform;
 
-				var object3D = build.clone();
+					if ( transform ) {
 
-				// apply component transform
+						object3D.applyMatrix4( transform );
 
-				var transform = component.transform;
-
-				if ( transform ) {
+					}
 
-					object3D.applyMatrix4( transform );
+					composite.add( object3D );
 
 				}
 
-				composite.add( object3D );
+				return composite;
 
 			}
 
-			return composite;
-
-		}
-
-		function buildObject( objectId, objects, modelData, textureData ) {
+			function buildObject( objectId, objects, modelData, textureData ) {
 
-			var objectData = modelData[ 'resources' ][ 'object' ][ objectId ];
+				var objectData = modelData[ 'resources' ][ 'object' ][ objectId ];
 
-			if ( objectData[ 'mesh' ] ) {
+				if ( objectData[ 'mesh' ] ) {
 
-				var meshData = objectData[ 'mesh' ];
+					var meshData = objectData[ 'mesh' ];
+					var extensions = modelData[ 'extensions' ];
+					var modelXml = modelData[ 'xml' ];
+					applyExtensions( extensions, meshData, modelXml );
+					objects[ objectData.id ] = getBuild( meshData, objects, modelData, textureData, objectData, buildGroup );
 
-				var extensions = modelData[ 'extensions' ];
-				var modelXml = modelData[ 'xml' ];
-
-				applyExtensions( extensions, meshData, modelXml );
-
-				objects[ objectData.id ] = getBuild( meshData, objects, modelData, textureData, objectData, buildGroup );
-
-			} else {
+				} else {
 
-				var compositeData = objectData[ 'components' ];
+					var compositeData = objectData[ 'components' ];
+					objects[ objectData.id ] = getBuild( compositeData, objects, modelData, textureData, objectData, buildComposite );
 
-				objects[ objectData.id ] = getBuild( compositeData, objects, modelData, textureData, objectData, buildComposite );
+				}
 
 			}
 
-		}
-
-		function buildObjects( data3mf ) {
+			function buildObjects( data3mf ) {
 
-			var modelsData = data3mf.model;
-			var modelRels = data3mf.modelRels;
-			var objects = {};
-			var modelsKeys = Object.keys( modelsData );
-			var textureData = {};
+				var modelsData = data3mf.model;
+				var modelRels = data3mf.modelRels;
+				var objects = {};
+				var modelsKeys = Object.keys( modelsData );
+				var textureData = {}; // evaluate model relationships to textures
 
-			// evaluate model relationships to textures
+				if ( modelRels ) {
 
-			if ( modelRels ) {
+					for ( var i = 0, l = modelRels.length; i < l; i ++ ) {
 
-				for ( var i = 0, l = modelRels.length; i < l; i ++ ) {
+						var modelRel = modelRels[ i ];
+						var textureKey = modelRel.target.substring( 1 );
 
-					var modelRel = modelRels[ i ];
-					var textureKey = modelRel.target.substring( 1 );
+						if ( data3mf.texture[ textureKey ] ) {
 
-					if ( data3mf.texture[ textureKey ] ) {
+							textureData[ modelRel.target ] = data3mf.texture[ textureKey ];
 
-						textureData[ modelRel.target ] = data3mf.texture[ textureKey ];
+						}
 
 					}
 
-				}
-
-			}
-
-			// start build
+				} // start build
 
-			for ( var i = 0; i < modelsKeys.length; i ++ ) {
 
-				var modelsKey = modelsKeys[ i ];
-				var modelData = modelsData[ modelsKey ];
+				for ( var i = 0; i < modelsKeys.length; i ++ ) {
 
-				var objectIds = Object.keys( modelData[ 'resources' ][ 'object' ] );
+					var modelsKey = modelsKeys[ i ];
+					var modelData = modelsData[ modelsKey ];
+					var objectIds = Object.keys( modelData[ 'resources' ][ 'object' ] );
 
-				for ( var j = 0; j < objectIds.length; j ++ ) {
+					for ( var j = 0; j < objectIds.length; j ++ ) {
 
-					var objectId = objectIds[ j ];
+						var objectId = objectIds[ j ];
+						buildObject( objectId, objects, modelData, textureData );
 
-					buildObject( objectId, objects, modelData, textureData );
+					}
 
 				}
 
-			}
+				return objects;
 
-			return objects;
-
-		}
+			}
 
-		function fetch3DModelPart( rels ) {
+			function fetch3DModelPart( rels ) {
 
-			for ( var i = 0; i < rels.length; i ++ ) {
+				for ( var i = 0; i < rels.length; i ++ ) {
 
-				var rel = rels[ i ];
-				var extension = rel.target.split( '.' ).pop();
+					var rel = rels[ i ];
+					var extension = rel.target.split( '.' ).pop();
+					if ( extension.toLowerCase() === 'model' ) return rel;
 
-				if ( extension.toLowerCase() === 'model' ) return rel;
+				}
 
 			}
 
-		}
-
-		function build( objects, data3mf ) {
+			function build( objects, data3mf ) {
 
-			var group = new THREE.Group();
+				var group = new THREE.Group();
+				var relationship = fetch3DModelPart( data3mf[ 'rels' ] );
+				var buildData = data3mf.model[ relationship[ 'target' ].substring( 1 ) ][ 'build' ];
 
-			var relationship = fetch3DModelPart( data3mf[ 'rels' ] );
-			var buildData = data3mf.model[ relationship[ 'target' ].substring( 1 ) ][ 'build' ];
+				for ( var i = 0; i < buildData.length; i ++ ) {
 
-			for ( var i = 0; i < buildData.length; i ++ ) {
+					var buildItem = buildData[ i ];
+					var object3D = objects[ buildItem[ 'objectId' ] ]; // apply transform
 
-				var buildItem = buildData[ i ];
-				var object3D = objects[ buildItem[ 'objectId' ] ];
+					var transform = buildItem[ 'transform' ];
 
-				// apply transform
+					if ( transform ) {
 
-				var transform = buildItem[ 'transform' ];
+						object3D.applyMatrix4( transform );
 
-				if ( transform ) {
+					}
 
-					object3D.applyMatrix4( transform );
+					group.add( object3D );
 
 				}
 
-				group.add( object3D );
+				return group;
 
 			}
 
-			return group;
+			var data3mf = loadDocument( data );
+			var objects = buildObjects( data3mf );
+			return build( objects, data3mf );
 
-		}
+		},
+		addExtension: function ( extension ) {
 
-		var data3mf = loadDocument( data );
-		var objects = buildObjects( data3mf );
+			this.availableExtensions.push( extension );
 
-		return build( objects, data3mf );
-
-	},
-
-	addExtension: function ( extension ) {
-
-		this.availableExtensions.push( extension );
+		}
+	} );
 
-	}
+	THREE.ThreeMFLoader = ThreeMFLoader;
 
-} );
+} )();

+ 290 - 268
examples/js/loaders/AMFLoader.js

@@ -1,5 +1,7 @@
-/**
- * Description: Early release of an AMF Loader following the pattern of the
+( function () {
+
+	/**
+ * Description: Early release of an AMF THREE.Loader following the pattern of the
  * example loaders in the three.js project.
  *
  * More information about the AMF format: http://amf.wikispaces.com
@@ -16,492 +18,512 @@
  *
  */
 
-THREE.AMFLoader = function ( manager ) {
-
-	THREE.Loader.call( this, manager );
+	var AMFLoader = function ( manager ) {
 
-};
+		THREE.Loader.call( this, manager );
 
-THREE.AMFLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+	};
 
-	constructor: THREE.AMFLoader,
+	AMFLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+		constructor: AMFLoader,
+		load: function ( url, onLoad, onProgress, onError ) {
 
-	load: function ( url, onLoad, onProgress, onError ) {
+			var scope = this;
+			var loader = new THREE.FileLoader( scope.manager );
+			loader.setPath( scope.path );
+			loader.setResponseType( 'arraybuffer' );
+			loader.setRequestHeader( scope.requestHeader );
+			loader.setWithCredentials( scope.withCredentials );
+			loader.load( url, function ( text ) {
 
-		var scope = this;
+				try {
 
-		var loader = new THREE.FileLoader( scope.manager );
-		loader.setPath( scope.path );
-		loader.setResponseType( 'arraybuffer' );
-		loader.setRequestHeader( scope.requestHeader );
-		loader.setWithCredentials( scope.withCredentials );
-		loader.load( url, function ( text ) {
+					onLoad( scope.parse( text ) );
 
-			try {
+				} catch ( e ) {
 
-				onLoad( scope.parse( text ) );
+					if ( onError ) {
 
-			} catch ( e ) {
+						onError( e );
 
-				if ( onError ) {
+					} else {
 
-					onError( e );
+						console.error( e );
 
-				} else {
+					}
 
-					console.error( e );
+					scope.manager.itemError( url );
 
 				}
 
-				scope.manager.itemError( url );
-
-			}
-
-		}, onProgress, onError );
-
-	},
+			}, onProgress, onError );
 
-	parse: function ( data ) {
+		},
+		parse: function ( data ) {
 
-		function loadDocument( data ) {
+			function loadDocument( data ) {
 
-			var view = new DataView( data );
-			var magic = String.fromCharCode( view.getUint8( 0 ), view.getUint8( 1 ) );
+				var view = new DataView( data );
+				var magic = String.fromCharCode( view.getUint8( 0 ), view.getUint8( 1 ) );
 
-			if ( magic === 'PK' ) {
+				if ( magic === 'PK' ) {
 
-				var zip = null;
-				var file = null;
+					var zip = null;
+					var file = null;
+					console.log( 'THREE.AMFLoader: Loading Zip' );
 
-				console.log( 'THREE.AMFLoader: Loading Zip' );
+					try {
 
-				try {
+						zip = fflate.unzipSync( new Uint8Array( data ) ); // eslint-disable-line no-undef
 
-					zip = fflate.unzipSync( new Uint8Array( data ) ); // eslint-disable-line no-undef
+					} catch ( e ) {
 
-				} catch ( e ) {
+						if ( e instanceof ReferenceError ) {
 
-					if ( e instanceof ReferenceError ) {
+							console.log( 'THREE.AMFLoader: fflate missing and file is compressed.' );
+							return null;
 
-						console.log( 'THREE.AMFLoader: fflate missing and file is compressed.' );
-						return null;
+						}
 
 					}
 
-				}
+					for ( var file in zip ) {
 
-				for ( var file in zip ) {
+						if ( file.toLowerCase().substr( - 4 ) === '.amf' ) {
 
-					if ( file.toLowerCase().substr( - 4 ) === '.amf' ) {
+							break;
 
-						break;
+						}
 
 					}
 
+					console.log( 'THREE.AMFLoader: Trying to load file asset: ' + file );
+					view = new DataView( zip[ file ].buffer );
+
 				}
 
-				console.log( 'THREE.AMFLoader: Trying to load file asset: ' + file );
-				view = new DataView( zip[ file ].buffer );
+				var fileText = THREE.LoaderUtils.decodeText( view );
+				var xmlData = new DOMParser().parseFromString( fileText, 'application/xml' );
 
-			}
+				if ( xmlData.documentElement.nodeName.toLowerCase() !== 'amf' ) {
 
-			var fileText = THREE.LoaderUtils.decodeText( view );
-			var xmlData = new DOMParser().parseFromString( fileText, 'application/xml' );
+					console.log( 'THREE.AMFLoader: Error loading AMF - no AMF document found.' );
+					return null;
 
-			if ( xmlData.documentElement.nodeName.toLowerCase() !== 'amf' ) {
+				}
 
-				console.log( 'THREE.AMFLoader: Error loading AMF - no AMF document found.' );
-				return null;
+				return xmlData;
 
 			}
 
-			return xmlData;
+			function loadDocumentScale( node ) {
 
-		}
+				var scale = 1.0;
+				var unit = 'millimeter';
 
-		function loadDocumentScale( node ) {
+				if ( node.documentElement.attributes.unit !== undefined ) {
 
-			var scale = 1.0;
-			var unit = 'millimeter';
+					unit = node.documentElement.attributes.unit.value.toLowerCase();
 
-			if ( node.documentElement.attributes.unit !== undefined ) {
+				}
 
-				unit = node.documentElement.attributes.unit.value.toLowerCase();
+				var scaleUnits = {
+					millimeter: 1.0,
+					inch: 25.4,
+					feet: 304.8,
+					meter: 1000.0,
+					micron: 0.001
+				};
 
-			}
+				if ( scaleUnits[ unit ] !== undefined ) {
 
-			var scaleUnits = {
-				millimeter: 1.0,
-				inch: 25.4,
-				feet: 304.8,
-				meter: 1000.0,
-				micron: 0.001
-			};
+					scale = scaleUnits[ unit ];
 
-			if ( scaleUnits[ unit ] !== undefined ) {
+				}
 
-				scale = scaleUnits[ unit ];
+				console.log( 'THREE.AMFLoader: Unit scale: ' + scale );
+				return scale;
 
 			}
 
-			console.log( 'THREE.AMFLoader: Unit scale: ' + scale );
-			return scale;
+			function loadMaterials( node ) {
 
-		}
+				var matName = 'AMF Material';
+				var matId = node.attributes.id.textContent;
+				var color = {
+					r: 1.0,
+					g: 1.0,
+					b: 1.0,
+					a: 1.0
+				};
+				var loadedMaterial = null;
 
-		function loadMaterials( node ) {
+				for ( var i = 0; i < node.childNodes.length; i ++ ) {
 
-			var matName = 'AMF Material';
-			var matId = node.attributes.id.textContent;
-			var color = { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };
+					var matChildEl = node.childNodes[ i ];
 
-			var loadedMaterial = null;
+					if ( matChildEl.nodeName === 'metadata' && matChildEl.attributes.type !== undefined ) {
 
-			for ( var i = 0; i < node.childNodes.length; i ++ ) {
+						if ( matChildEl.attributes.type.value === 'name' ) {
 
-				var matChildEl = node.childNodes[ i ];
+							matName = matChildEl.textContent;
 
-				if ( matChildEl.nodeName === 'metadata' && matChildEl.attributes.type !== undefined ) {
+						}
 
-					if ( matChildEl.attributes.type.value === 'name' ) {
+					} else if ( matChildEl.nodeName === 'color' ) {
 
-						matName = matChildEl.textContent;
+						color = loadColor( matChildEl );
 
 					}
 
-				} else if ( matChildEl.nodeName === 'color' ) {
-
-					color = loadColor( matChildEl );
-
 				}
 
-			}
+				loadedMaterial = new THREE.MeshPhongMaterial( {
+					flatShading: true,
+					color: new THREE.Color( color.r, color.g, color.b ),
+					name: matName
+				} );
 
-			loadedMaterial = new THREE.MeshPhongMaterial( {
-				flatShading: true,
-				color: new THREE.Color( color.r, color.g, color.b ),
-				name: matName
-			} );
+				if ( color.a !== 1.0 ) {
 
-			if ( color.a !== 1.0 ) {
+					loadedMaterial.transparent = true;
+					loadedMaterial.opacity = color.a;
 
-				loadedMaterial.transparent = true;
-				loadedMaterial.opacity = color.a;
+				}
 
-			}
+				return {
+					id: matId,
+					material: loadedMaterial
+				};
 
-			return { id: matId, material: loadedMaterial };
+			}
 
-		}
+			function loadColor( node ) {
 
-		function loadColor( node ) {
+				var color = {
+					r: 1.0,
+					g: 1.0,
+					b: 1.0,
+					a: 1.0
+				};
 
-			var color = { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };
+				for ( var i = 0; i < node.childNodes.length; i ++ ) {
 
-			for ( var i = 0; i < node.childNodes.length; i ++ ) {
+					var matColor = node.childNodes[ i ];
 
-				var matColor = node.childNodes[ i ];
+					if ( matColor.nodeName === 'r' ) {
 
-				if ( matColor.nodeName === 'r' ) {
+						color.r = matColor.textContent;
 
-					color.r = matColor.textContent;
+					} else if ( matColor.nodeName === 'g' ) {
 
-				} else if ( matColor.nodeName === 'g' ) {
+						color.g = matColor.textContent;
 
-					color.g = matColor.textContent;
+					} else if ( matColor.nodeName === 'b' ) {
 
-				} else if ( matColor.nodeName === 'b' ) {
+						color.b = matColor.textContent;
 
-					color.b = matColor.textContent;
+					} else if ( matColor.nodeName === 'a' ) {
 
-				} else if ( matColor.nodeName === 'a' ) {
+						color.a = matColor.textContent;
 
-					color.a = matColor.textContent;
+					}
 
 				}
 
-			}
-
-			return color;
+				return color;
 
-		}
+			}
 
-		function loadMeshVolume( node ) {
+			function loadMeshVolume( node ) {
 
-			var volume = { name: '', triangles: [], materialid: null };
+				var volume = {
+					name: '',
+					triangles: [],
+					materialid: null
+				};
+				var currVolumeNode = node.firstElementChild;
 
-			var currVolumeNode = node.firstElementChild;
+				if ( node.attributes.materialid !== undefined ) {
 
-			if ( node.attributes.materialid !== undefined ) {
+					volume.materialId = node.attributes.materialid.nodeValue;
 
-				volume.materialId = node.attributes.materialid.nodeValue;
+				}
 
-			}
+				while ( currVolumeNode ) {
 
-			while ( currVolumeNode ) {
+					if ( currVolumeNode.nodeName === 'metadata' ) {
 
-				if ( currVolumeNode.nodeName === 'metadata' ) {
+						if ( currVolumeNode.attributes.type !== undefined ) {
 
-					if ( currVolumeNode.attributes.type !== undefined ) {
+							if ( currVolumeNode.attributes.type.value === 'name' ) {
 
-						if ( currVolumeNode.attributes.type.value === 'name' ) {
+								volume.name = currVolumeNode.textContent;
 
-							volume.name = currVolumeNode.textContent;
+							}
 
 						}
 
-					}
+					} else if ( currVolumeNode.nodeName === 'triangle' ) {
 
-				} else if ( currVolumeNode.nodeName === 'triangle' ) {
+						var v1 = currVolumeNode.getElementsByTagName( 'v1' )[ 0 ].textContent;
+						var v2 = currVolumeNode.getElementsByTagName( 'v2' )[ 0 ].textContent;
+						var v3 = currVolumeNode.getElementsByTagName( 'v3' )[ 0 ].textContent;
+						volume.triangles.push( v1, v2, v3 );
 
-					var v1 = currVolumeNode.getElementsByTagName( 'v1' )[ 0 ].textContent;
-					var v2 = currVolumeNode.getElementsByTagName( 'v2' )[ 0 ].textContent;
-					var v3 = currVolumeNode.getElementsByTagName( 'v3' )[ 0 ].textContent;
+					}
 
-					volume.triangles.push( v1, v2, v3 );
+					currVolumeNode = currVolumeNode.nextElementSibling;
 
 				}
 
-				currVolumeNode = currVolumeNode.nextElementSibling;
+				return volume;
 
 			}
 
-			return volume;
+			function loadMeshVertices( node ) {
 
-		}
-
-		function loadMeshVertices( node ) {
+				var vertArray = [];
+				var normalArray = [];
+				var currVerticesNode = node.firstElementChild;
 
-			var vertArray = [];
-			var normalArray = [];
-			var currVerticesNode = node.firstElementChild;
+				while ( currVerticesNode ) {
 
-			while ( currVerticesNode ) {
+					if ( currVerticesNode.nodeName === 'vertex' ) {
 
-				if ( currVerticesNode.nodeName === 'vertex' ) {
+						var vNode = currVerticesNode.firstElementChild;
 
-					var vNode = currVerticesNode.firstElementChild;
+						while ( vNode ) {
 
-					while ( vNode ) {
+							if ( vNode.nodeName === 'coordinates' ) {
 
-						if ( vNode.nodeName === 'coordinates' ) {
+								var x = vNode.getElementsByTagName( 'x' )[ 0 ].textContent;
+								var y = vNode.getElementsByTagName( 'y' )[ 0 ].textContent;
+								var z = vNode.getElementsByTagName( 'z' )[ 0 ].textContent;
+								vertArray.push( x, y, z );
 
-							var x = vNode.getElementsByTagName( 'x' )[ 0 ].textContent;
-							var y = vNode.getElementsByTagName( 'y' )[ 0 ].textContent;
-							var z = vNode.getElementsByTagName( 'z' )[ 0 ].textContent;
+							} else if ( vNode.nodeName === 'normal' ) {
 
-							vertArray.push( x, y, z );
+								var nx = vNode.getElementsByTagName( 'nx' )[ 0 ].textContent;
+								var ny = vNode.getElementsByTagName( 'ny' )[ 0 ].textContent;
+								var nz = vNode.getElementsByTagName( 'nz' )[ 0 ].textContent;
+								normalArray.push( nx, ny, nz );
 
-						} else if ( vNode.nodeName === 'normal' ) {
+							}
 
-							var nx = vNode.getElementsByTagName( 'nx' )[ 0 ].textContent;
-							var ny = vNode.getElementsByTagName( 'ny' )[ 0 ].textContent;
-							var nz = vNode.getElementsByTagName( 'nz' )[ 0 ].textContent;
-
-							normalArray.push( nx, ny, nz );
+							vNode = vNode.nextElementSibling;
 
 						}
 
-						vNode = vNode.nextElementSibling;
-
 					}
 
+					currVerticesNode = currVerticesNode.nextElementSibling;
+
 				}
 
-				currVerticesNode = currVerticesNode.nextElementSibling;
+				return {
+					'vertices': vertArray,
+					'normals': normalArray
+				};
 
 			}
 
-			return { 'vertices': vertArray, 'normals': normalArray };
-
-		}
+			function loadObject( node ) {
 
-		function loadObject( node ) {
+				var objId = node.attributes.id.textContent;
+				var loadedObject = {
+					name: 'amfobject',
+					meshes: []
+				};
+				var currColor = null;
+				var currObjNode = node.firstElementChild;
 
-			var objId = node.attributes.id.textContent;
-			var loadedObject = { name: 'amfobject', meshes: [] };
-			var currColor = null;
-			var currObjNode = node.firstElementChild;
+				while ( currObjNode ) {
 
-			while ( currObjNode ) {
+					if ( currObjNode.nodeName === 'metadata' ) {
 
-				if ( currObjNode.nodeName === 'metadata' ) {
+						if ( currObjNode.attributes.type !== undefined ) {
 
-					if ( currObjNode.attributes.type !== undefined ) {
+							if ( currObjNode.attributes.type.value === 'name' ) {
 
-						if ( currObjNode.attributes.type.value === 'name' ) {
+								loadedObject.name = currObjNode.textContent;
 
-							loadedObject.name = currObjNode.textContent;
+							}
 
 						}
 
-					}
+					} else if ( currObjNode.nodeName === 'color' ) {
 
-				} else if ( currObjNode.nodeName === 'color' ) {
+						currColor = loadColor( currObjNode );
 
-					currColor = loadColor( currObjNode );
+					} else if ( currObjNode.nodeName === 'mesh' ) {
 
-				} else if ( currObjNode.nodeName === 'mesh' ) {
+						var currMeshNode = currObjNode.firstElementChild;
+						var mesh = {
+							vertices: [],
+							normals: [],
+							volumes: [],
+							color: currColor
+						};
 
-					var currMeshNode = currObjNode.firstElementChild;
-					var mesh = { vertices: [], normals: [], volumes: [], color: currColor };
+						while ( currMeshNode ) {
 
-					while ( currMeshNode ) {
+							if ( currMeshNode.nodeName === 'vertices' ) {
 
-						if ( currMeshNode.nodeName === 'vertices' ) {
+								var loadedVertices = loadMeshVertices( currMeshNode );
+								mesh.normals = mesh.normals.concat( loadedVertices.normals );
+								mesh.vertices = mesh.vertices.concat( loadedVertices.vertices );
 
-							var loadedVertices = loadMeshVertices( currMeshNode );
+							} else if ( currMeshNode.nodeName === 'volume' ) {
 
-							mesh.normals = mesh.normals.concat( loadedVertices.normals );
-							mesh.vertices = mesh.vertices.concat( loadedVertices.vertices );
+								mesh.volumes.push( loadMeshVolume( currMeshNode ) );
 
-						} else if ( currMeshNode.nodeName === 'volume' ) {
+							}
 
-							mesh.volumes.push( loadMeshVolume( currMeshNode ) );
+							currMeshNode = currMeshNode.nextElementSibling;
 
 						}
 
-						currMeshNode = currMeshNode.nextElementSibling;
+						loadedObject.meshes.push( mesh );
 
 					}
 
-					loadedObject.meshes.push( mesh );
+					currObjNode = currObjNode.nextElementSibling;
 
 				}
 
-				currObjNode = currObjNode.nextElementSibling;
+				return {
+					'id': objId,
+					'obj': loadedObject
+				};
 
 			}
 
-			return { 'id': objId, 'obj': loadedObject };
+			var xmlData = loadDocument( data );
+			var amfName = '';
+			var amfAuthor = '';
+			var amfScale = loadDocumentScale( xmlData );
+			var amfMaterials = {};
+			var amfObjects = {};
+			var childNodes = xmlData.documentElement.childNodes;
+			var i, j;
 
-		}
+			for ( i = 0; i < childNodes.length; i ++ ) {
 
-		var xmlData = loadDocument( data );
-		var amfName = '';
-		var amfAuthor = '';
-		var amfScale = loadDocumentScale( xmlData );
-		var amfMaterials = {};
-		var amfObjects = {};
-		var childNodes = xmlData.documentElement.childNodes;
+				var child = childNodes[ i ];
 
-		var i, j;
+				if ( child.nodeName === 'metadata' ) {
 
-		for ( i = 0; i < childNodes.length; i ++ ) {
+					if ( child.attributes.type !== undefined ) {
 
-			var child = childNodes[ i ];
+						if ( child.attributes.type.value === 'name' ) {
 
-			if ( child.nodeName === 'metadata' ) {
+							amfName = child.textContent;
 
-				if ( child.attributes.type !== undefined ) {
+						} else if ( child.attributes.type.value === 'author' ) {
 
-					if ( child.attributes.type.value === 'name' ) {
+							amfAuthor = child.textContent;
 
-						amfName = child.textContent;
-
-					} else if ( child.attributes.type.value === 'author' ) {
-
-						amfAuthor = child.textContent;
+						}
 
 					}
 
-				}
+				} else if ( child.nodeName === 'material' ) {
 
-			} else if ( child.nodeName === 'material' ) {
+					var loadedMaterial = loadMaterials( child );
+					amfMaterials[ loadedMaterial.id ] = loadedMaterial.material;
 
-				var loadedMaterial = loadMaterials( child );
+				} else if ( child.nodeName === 'object' ) {
 
-				amfMaterials[ loadedMaterial.id ] = loadedMaterial.material;
+					var loadedObject = loadObject( child );
+					amfObjects[ loadedObject.id ] = loadedObject.obj;
 
-			} else if ( child.nodeName === 'object' ) {
-
-				var loadedObject = loadObject( child );
-
-				amfObjects[ loadedObject.id ] = loadedObject.obj;
+				}
 
 			}
 
-		}
-
-		var sceneObject = new THREE.Group();
-		var defaultMaterial = new THREE.MeshPhongMaterial( { color: 0xaaaaff, flatShading: true } );
-
-		sceneObject.name = amfName;
-		sceneObject.userData.author = amfAuthor;
-		sceneObject.userData.loader = 'AMF';
+			var sceneObject = new THREE.Group();
+			var defaultMaterial = new THREE.MeshPhongMaterial( {
+				color: 0xaaaaff,
+				flatShading: true
+			} );
+			sceneObject.name = amfName;
+			sceneObject.userData.author = amfAuthor;
+			sceneObject.userData.loader = 'AMF';
 
-		for ( var id in amfObjects ) {
+			for ( var id in amfObjects ) {
 
-			var part = amfObjects[ id ];
-			var meshes = part.meshes;
-			var newObject = new THREE.Group();
-			newObject.name = part.name || '';
+				var part = amfObjects[ id ];
+				var meshes = part.meshes;
+				var newObject = new THREE.Group();
+				newObject.name = part.name || '';
 
-			for ( i = 0; i < meshes.length; i ++ ) {
+				for ( i = 0; i < meshes.length; i ++ ) {
 
-				var objDefaultMaterial = defaultMaterial;
-				var mesh = meshes[ i ];
-				var vertices = new THREE.Float32BufferAttribute( mesh.vertices, 3 );
-				var normals = null;
+					var objDefaultMaterial = defaultMaterial;
+					var mesh = meshes[ i ];
+					var vertices = new THREE.Float32BufferAttribute( mesh.vertices, 3 );
+					var normals = null;
 
-				if ( mesh.normals.length ) {
+					if ( mesh.normals.length ) {
 
-					normals = new THREE.Float32BufferAttribute( mesh.normals, 3 );
+						normals = new THREE.Float32BufferAttribute( mesh.normals, 3 );
 
-				}
+					}
 
-				if ( mesh.color ) {
+					if ( mesh.color ) {
 
-					var color = mesh.color;
+						var color = mesh.color;
+						objDefaultMaterial = defaultMaterial.clone();
+						objDefaultMaterial.color = new THREE.Color( color.r, color.g, color.b );
 
-					objDefaultMaterial = defaultMaterial.clone();
-					objDefaultMaterial.color = new THREE.Color( color.r, color.g, color.b );
+						if ( color.a !== 1.0 ) {
 
-					if ( color.a !== 1.0 ) {
+							objDefaultMaterial.transparent = true;
+							objDefaultMaterial.opacity = color.a;
 
-						objDefaultMaterial.transparent = true;
-						objDefaultMaterial.opacity = color.a;
+						}
 
 					}
 
-				}
+					var volumes = mesh.volumes;
 
-				var volumes = mesh.volumes;
+					for ( j = 0; j < volumes.length; j ++ ) {
 
-				for ( j = 0; j < volumes.length; j ++ ) {
+						var volume = volumes[ j ];
+						var newGeometry = new THREE.BufferGeometry();
+						var material = objDefaultMaterial;
+						newGeometry.setIndex( volume.triangles );
+						newGeometry.setAttribute( 'position', vertices.clone() );
 
-					var volume = volumes[ j ];
-					var newGeometry = new THREE.BufferGeometry();
-					var material = objDefaultMaterial;
+						if ( normals ) {
 
-					newGeometry.setIndex( volume.triangles );
-					newGeometry.setAttribute( 'position', vertices.clone() );
+							newGeometry.setAttribute( 'normal', normals.clone() );
 
-					if ( normals ) {
+						}
 
-						newGeometry.setAttribute( 'normal', normals.clone() );
+						if ( amfMaterials[ volume.materialId ] !== undefined ) {
 
-					}
+							material = amfMaterials[ volume.materialId ];
 
-					if ( amfMaterials[ volume.materialId ] !== undefined ) {
+						}
 
-						material = amfMaterials[ volume.materialId ];
+						newGeometry.scale( amfScale, amfScale, amfScale );
+						newObject.add( new THREE.Mesh( newGeometry, material.clone() ) );
 
 					}
 
-					newGeometry.scale( amfScale, amfScale, amfScale );
-					newObject.add( new THREE.Mesh( newGeometry, material.clone() ) );
-
 				}
 
+				sceneObject.add( newObject );
+
 			}
 
-			sceneObject.add( newObject );
+			return sceneObject;
 
 		}
+	} );
 
-		return sceneObject;
-
-	}
+	THREE.AMFLoader = AMFLoader;
 
-} );
+} )();

+ 225 - 258
examples/js/loaders/BVHLoader.js

@@ -1,425 +1,392 @@
-/**
+( function () {
+
+	/**
  * Description: reads BVH files and outputs a single THREE.Skeleton and an THREE.AnimationClip
  *
  * Currently only supports bvh files containing a single root.
  *
  */
 
-THREE.BVHLoader = function ( manager ) {
-
-	THREE.Loader.call( this, manager );
+	var BVHLoader = function ( manager ) {
 
-	this.animateBonePositions = true;
-	this.animateBoneRotations = true;
+		THREE.Loader.call( this, manager );
+		this.animateBonePositions = true;
+		this.animateBoneRotations = true;
 
-};
+	};
 
-THREE.BVHLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+	BVHLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+		constructor: BVHLoader,
+		load: function ( url, onLoad, onProgress, onError ) {
 
-	constructor: THREE.BVHLoader,
+			var scope = this;
+			var loader = new THREE.FileLoader( scope.manager );
+			loader.setPath( scope.path );
+			loader.setRequestHeader( scope.requestHeader );
+			loader.setWithCredentials( scope.withCredentials );
+			loader.load( url, function ( text ) {
 
-	load: function ( url, onLoad, onProgress, onError ) {
+				try {
 
-		var scope = this;
+					onLoad( scope.parse( text ) );
 
-		var loader = new THREE.FileLoader( scope.manager );
-		loader.setPath( scope.path );
-		loader.setRequestHeader( scope.requestHeader );
-		loader.setWithCredentials( scope.withCredentials );
-		loader.load( url, function ( text ) {
+				} catch ( e ) {
 
-			try {
+					if ( onError ) {
 
-				onLoad( scope.parse( text ) );
+						onError( e );
 
-			} catch ( e ) {
+					} else {
 
-				if ( onError ) {
+						console.error( e );
 
-					onError( e );
+					}
 
-				} else {
-
-					console.error( e );
+					scope.manager.itemError( url );
 
 				}
 
-				scope.manager.itemError( url );
-
-			}
-
-		}, onProgress, onError );
-
-	},
+			}, onProgress, onError );
 
-	parse: function ( text ) {
+		},
+		parse: function ( text ) {
 
-		/*
+			/*
 			reads a string array (lines) from a BVH file
 			and outputs a skeleton structure including motion data
-
-			returns thee root node:
+				returns thee root node:
 			{ name: '', channels: [], children: [] }
 		*/
-		function readBvh( lines ) {
-
-			// read model structure
+			function readBvh( lines ) {
 
-			if ( nextLine( lines ) !== 'HIERARCHY' ) {
+				// read model structure
+				if ( nextLine( lines ) !== 'HIERARCHY' ) {
 
-				console.error( 'THREE.BVHLoader: HIERARCHY expected.' );
+					console.error( 'THREE.BVHLoader: HIERARCHY expected.' );
 
-			}
+				}
 
-			var list = []; // collects flat array of all bones
-			var root = readNode( lines, nextLine( lines ), list );
+				var list = []; // collects flat array of all bones
 
-			// read motion data
+				var root = readNode( lines, nextLine( lines ), list ); // read motion data
 
-			if ( nextLine( lines ) !== 'MOTION' ) {
+				if ( nextLine( lines ) !== 'MOTION' ) {
 
-				console.error( 'THREE.BVHLoader: MOTION expected.' );
+					console.error( 'THREE.BVHLoader: MOTION expected.' );
 
-			}
+				} // number of frames
 
-			// number of frames
 
-			var tokens = nextLine( lines ).split( /[\s]+/ );
-			var numFrames = parseInt( tokens[ 1 ] );
+				var tokens = nextLine( lines ).split( /[\s]+/ );
+				var numFrames = parseInt( tokens[ 1 ] );
 
-			if ( isNaN( numFrames ) ) {
+				if ( isNaN( numFrames ) ) {
 
-				console.error( 'THREE.BVHLoader: Failed to read number of frames.' );
-
-			}
+					console.error( 'THREE.BVHLoader: Failed to read number of frames.' );
 
-			// frame time
+				} // frame time
 
-			tokens = nextLine( lines ).split( /[\s]+/ );
-			var frameTime = parseFloat( tokens[ 2 ] );
 
-			if ( isNaN( frameTime ) ) {
+				tokens = nextLine( lines ).split( /[\s]+/ );
+				var frameTime = parseFloat( tokens[ 2 ] );
 
-				console.error( 'THREE.BVHLoader: Failed to read frame time.' );
+				if ( isNaN( frameTime ) ) {
 
-			}
+					console.error( 'THREE.BVHLoader: Failed to read frame time.' );
 
-			// read frame data line by line
+				} // read frame data line by line
 
-			for ( var i = 0; i < numFrames; i ++ ) {
 
-				tokens = nextLine( lines ).split( /[\s]+/ );
-				readFrameData( tokens, i * frameTime, root );
+				for ( var i = 0; i < numFrames; i ++ ) {
 
-			}
+					tokens = nextLine( lines ).split( /[\s]+/ );
+					readFrameData( tokens, i * frameTime, root );
 
-			return list;
+				}
 
-		}
+				return list;
 
-		/*
+			}
+			/*
 			Recursively reads data from a single frame into the bone hierarchy.
 			The passed bone hierarchy has to be structured in the same order as the BVH file.
 			keyframe data is stored in bone.frames.
-
-			- data: splitted string array (frame values), values are shift()ed so
+				- data: splitted string array (frame values), values are shift()ed so
 			this should be empty after parsing the whole hierarchy.
 			- frameTime: playback time for this keyframe.
 			- bone: the bone to read frame data from.
 		*/
-		function readFrameData( data, frameTime, bone ) {
 
-			// end sites have no motion data
 
-			if ( bone.type === 'ENDSITE' ) return;
+			function readFrameData( data, frameTime, bone ) {
 
-			// add keyframe
+				// end sites have no motion data
+				if ( bone.type === 'ENDSITE' ) return; // add keyframe
 
-			var keyframe = {
-				time: frameTime,
-				position: new THREE.Vector3(),
-				rotation: new THREE.Quaternion()
-			};
+				var keyframe = {
+					time: frameTime,
+					position: new THREE.Vector3(),
+					rotation: new THREE.Quaternion()
+				};
+				bone.frames.push( keyframe );
+				var quat = new THREE.Quaternion();
+				var vx = new THREE.Vector3( 1, 0, 0 );
+				var vy = new THREE.Vector3( 0, 1, 0 );
+				var vz = new THREE.Vector3( 0, 0, 1 ); // parse values for each channel in node
 
-			bone.frames.push( keyframe );
-
-			var quat = new THREE.Quaternion();
-
-			var vx = new THREE.Vector3( 1, 0, 0 );
-			var vy = new THREE.Vector3( 0, 1, 0 );
-			var vz = new THREE.Vector3( 0, 0, 1 );
-
-			// parse values for each channel in node
-
-			for ( var i = 0; i < bone.channels.length; i ++ ) {
-
-				switch ( bone.channels[ i ] ) {
-
-					case 'Xposition':
-						keyframe.position.x = parseFloat( data.shift().trim() );
-						break;
-					case 'Yposition':
-						keyframe.position.y = parseFloat( data.shift().trim() );
-						break;
-					case 'Zposition':
-						keyframe.position.z = parseFloat( data.shift().trim() );
-						break;
-					case 'Xrotation':
-						quat.setFromAxisAngle( vx, parseFloat( data.shift().trim() ) * Math.PI / 180 );
-						keyframe.rotation.multiply( quat );
-						break;
-					case 'Yrotation':
-						quat.setFromAxisAngle( vy, parseFloat( data.shift().trim() ) * Math.PI / 180 );
-						keyframe.rotation.multiply( quat );
-						break;
-					case 'Zrotation':
-						quat.setFromAxisAngle( vz, parseFloat( data.shift().trim() ) * Math.PI / 180 );
-						keyframe.rotation.multiply( quat );
-						break;
-					default:
-						console.warn( 'THREE.BVHLoader: Invalid channel type.' );
+				for ( var i = 0; i < bone.channels.length; i ++ ) {
 
-				}
+					switch ( bone.channels[ i ] ) {
 
-			}
+						case 'Xposition':
+							keyframe.position.x = parseFloat( data.shift().trim() );
+							break;
 
-			// parse child nodes
+						case 'Yposition':
+							keyframe.position.y = parseFloat( data.shift().trim() );
+							break;
 
-			for ( var i = 0; i < bone.children.length; i ++ ) {
+						case 'Zposition':
+							keyframe.position.z = parseFloat( data.shift().trim() );
+							break;
 
-				readFrameData( data, frameTime, bone.children[ i ] );
+						case 'Xrotation':
+							quat.setFromAxisAngle( vx, parseFloat( data.shift().trim() ) * Math.PI / 180 );
+							keyframe.rotation.multiply( quat );
+							break;
 
-			}
+						case 'Yrotation':
+							quat.setFromAxisAngle( vy, parseFloat( data.shift().trim() ) * Math.PI / 180 );
+							keyframe.rotation.multiply( quat );
+							break;
 
-		}
+						case 'Zrotation':
+							quat.setFromAxisAngle( vz, parseFloat( data.shift().trim() ) * Math.PI / 180 );
+							keyframe.rotation.multiply( quat );
+							break;
 
-		/*
-		 Recursively parses the HIERACHY section of the BVH file
+						default:
+							console.warn( 'THREE.BVHLoader: Invalid channel type.' );
+
+					}
 
-		 - lines: all lines of the file. lines are consumed as we go along.
+				} // parse child nodes
+
+
+				for ( var i = 0; i < bone.children.length; i ++ ) {
+
+					readFrameData( data, frameTime, bone.children[ i ] );
+
+				}
+
+			}
+			/*
+		 Recursively parses the HIERACHY section of the BVH file
+			 - lines: all lines of the file. lines are consumed as we go along.
 		 - firstline: line containing the node type and name e.g. 'JOINT hip'
 		 - list: collects a flat list of nodes
-
-		 returns: a BVH node including children
+			 returns: a BVH node including children
 		*/
-		function readNode( lines, firstline, list ) {
 
-			var node = { name: '', type: '', frames: [] };
-			list.push( node );
 
-			// parse node type and name
+			function readNode( lines, firstline, list ) {
+
+				var node = {
+					name: '',
+					type: '',
+					frames: []
+				};
+				list.push( node ); // parse node type and name
 
-			var tokens = firstline.split( /[\s]+/ );
+				var tokens = firstline.split( /[\s]+/ );
 
-			if ( tokens[ 0 ].toUpperCase() === 'END' && tokens[ 1 ].toUpperCase() === 'SITE' ) {
+				if ( tokens[ 0 ].toUpperCase() === 'END' && tokens[ 1 ].toUpperCase() === 'SITE' ) {
 
-				node.type = 'ENDSITE';
-				node.name = 'ENDSITE'; // bvh end sites have no name
+					node.type = 'ENDSITE';
+					node.name = 'ENDSITE'; // bvh end sites have no name
 
-			} else {
+				} else {
 
-				node.name = tokens[ 1 ];
-				node.type = tokens[ 0 ].toUpperCase();
+					node.name = tokens[ 1 ];
+					node.type = tokens[ 0 ].toUpperCase();
 
-			}
+				}
 
-			if ( nextLine( lines ) !== '{' ) {
+				if ( nextLine( lines ) !== '{' ) {
 
-				console.error( 'THREE.BVHLoader: Expected opening { after type & name' );
+					console.error( 'THREE.BVHLoader: Expected opening { after type & name' );
 
-			}
+				} // parse OFFSET
 
-			// parse OFFSET
 
-			tokens = nextLine( lines ).split( /[\s]+/ );
+				tokens = nextLine( lines ).split( /[\s]+/ );
 
-			if ( tokens[ 0 ] !== 'OFFSET' ) {
+				if ( tokens[ 0 ] !== 'OFFSET' ) {
 
-				console.error( 'THREE.BVHLoader: Expected OFFSET but got: ' + tokens[ 0 ] );
+					console.error( 'THREE.BVHLoader: Expected OFFSET but got: ' + tokens[ 0 ] );
 
-			}
+				}
 
-			if ( tokens.length !== 4 ) {
+				if ( tokens.length !== 4 ) {
 
-				console.error( 'THREE.BVHLoader: Invalid number of values for OFFSET.' );
+					console.error( 'THREE.BVHLoader: Invalid number of values for OFFSET.' );
 
-			}
+				}
 
-			var offset = new THREE.Vector3(
-				parseFloat( tokens[ 1 ] ),
-				parseFloat( tokens[ 2 ] ),
-				parseFloat( tokens[ 3 ] )
-			);
+				var offset = new THREE.Vector3( parseFloat( tokens[ 1 ] ), parseFloat( tokens[ 2 ] ), parseFloat( tokens[ 3 ] ) );
 
-			if ( isNaN( offset.x ) || isNaN( offset.y ) || isNaN( offset.z ) ) {
+				if ( isNaN( offset.x ) || isNaN( offset.y ) || isNaN( offset.z ) ) {
 
-				console.error( 'THREE.BVHLoader: Invalid values of OFFSET.' );
+					console.error( 'THREE.BVHLoader: Invalid values of OFFSET.' );
 
-			}
+				}
 
-			node.offset = offset;
+				node.offset = offset; // parse CHANNELS definitions
 
-			// parse CHANNELS definitions
+				if ( node.type !== 'ENDSITE' ) {
 
-			if ( node.type !== 'ENDSITE' ) {
+					tokens = nextLine( lines ).split( /[\s]+/ );
 
-				tokens = nextLine( lines ).split( /[\s]+/ );
+					if ( tokens[ 0 ] !== 'CHANNELS' ) {
 
-				if ( tokens[ 0 ] !== 'CHANNELS' ) {
+						console.error( 'THREE.BVHLoader: Expected CHANNELS definition.' );
 
-					console.error( 'THREE.BVHLoader: Expected CHANNELS definition.' );
+					}
 
-				}
+					var numChannels = parseInt( tokens[ 1 ] );
+					node.channels = tokens.splice( 2, numChannels );
+					node.children = [];
 
-				var numChannels = parseInt( tokens[ 1 ] );
-				node.channels = tokens.splice( 2, numChannels );
-				node.children = [];
+				} // read children
 
-			}
 
-			// read children
+				while ( true ) {
 
-			while ( true ) {
+					var line = nextLine( lines );
 
-				var line = nextLine( lines );
+					if ( line === '}' ) {
 
-				if ( line === '}' ) {
+						return node;
 
-					return node;
+					} else {
 
-				} else {
+						node.children.push( readNode( lines, line, list ) );
 
-					node.children.push( readNode( lines, line, list ) );
+					}
 
 				}
 
 			}
-
-		}
-
-		/*
+			/*
 			recursively converts the internal bvh node structure to a THREE.Bone hierarchy
-
-			source: the bvh root node
+				source: the bvh root node
 			list: pass an empty array, collects a flat list of all converted THREE.Bones
-
-			returns the root THREE.Bone
+				returns the root THREE.Bone
 		*/
-		function toTHREEBone( source, list ) {
 
-			var bone = new THREE.Bone();
-			list.push( bone );
 
-			bone.position.add( source.offset );
-			bone.name = source.name;
+			function toTHREEBone( source, list ) {
 
-			if ( source.type !== 'ENDSITE' ) {
+				var bone = new THREE.Bone();
+				list.push( bone );
+				bone.position.add( source.offset );
+				bone.name = source.name;
 
-				for ( var i = 0; i < source.children.length; i ++ ) {
+				if ( source.type !== 'ENDSITE' ) {
 
-					bone.add( toTHREEBone( source.children[ i ], list ) );
+					for ( var i = 0; i < source.children.length; i ++ ) {
 
-				}
+						bone.add( toTHREEBone( source.children[ i ], list ) );
 
-			}
+					}
 
-			return bone;
+				}
 
-		}
+				return bone;
 
-		/*
+			}
+			/*
 			builds a THREE.AnimationClip from the keyframe data saved in each bone.
-
-			bone: bvh root node
-
-			returns: a THREE.AnimationClip containing position and quaternion tracks
+				bone: bvh root node
+				returns: a THREE.AnimationClip containing position and quaternion tracks
 		*/
-		function toTHREEAnimation( bones ) {
 
-			var tracks = [];
 
-			// create a position and quaternion animation track for each node
+			function toTHREEAnimation( bones ) {
 
-			for ( var i = 0; i < bones.length; i ++ ) {
+				var tracks = []; // create a position and quaternion animation track for each node
 
-				var bone = bones[ i ];
+				for ( var i = 0; i < bones.length; i ++ ) {
 
-				if ( bone.type === 'ENDSITE' )
-					continue;
+					var bone = bones[ i ];
+					if ( bone.type === 'ENDSITE' ) continue; // track data
 
-				// track data
+					var times = [];
+					var positions = [];
+					var rotations = [];
 
-				var times = [];
-				var positions = [];
-				var rotations = [];
+					for ( var j = 0; j < bone.frames.length; j ++ ) {
 
-				for ( var j = 0; j < bone.frames.length; j ++ ) {
+						var frame = bone.frames[ j ];
+						times.push( frame.time ); // the animation system animates the position property,
+						// so we have to add the joint offset to all values
 
-					var frame = bone.frames[ j ];
+						positions.push( frame.position.x + bone.offset.x );
+						positions.push( frame.position.y + bone.offset.y );
+						positions.push( frame.position.z + bone.offset.z );
+						rotations.push( frame.rotation.x );
+						rotations.push( frame.rotation.y );
+						rotations.push( frame.rotation.z );
+						rotations.push( frame.rotation.w );
 
-					times.push( frame.time );
+					}
 
-					// the animation system animates the position property,
-					// so we have to add the joint offset to all values
+					if ( scope.animateBonePositions ) {
 
-					positions.push( frame.position.x + bone.offset.x );
-					positions.push( frame.position.y + bone.offset.y );
-					positions.push( frame.position.z + bone.offset.z );
+						tracks.push( new THREE.VectorKeyframeTrack( '.bones[' + bone.name + '].position', times, positions ) );
 
-					rotations.push( frame.rotation.x );
-					rotations.push( frame.rotation.y );
-					rotations.push( frame.rotation.z );
-					rotations.push( frame.rotation.w );
+					}
 
-				}
+					if ( scope.animateBoneRotations ) {
 
-				if ( scope.animateBonePositions ) {
+						tracks.push( new THREE.QuaternionKeyframeTrack( '.bones[' + bone.name + '].quaternion', times, rotations ) );
 
-					tracks.push( new THREE.VectorKeyframeTrack( '.bones[' + bone.name + '].position', times, positions ) );
+					}
 
 				}
 
-				if ( scope.animateBoneRotations ) {
-
-					tracks.push( new THREE.QuaternionKeyframeTrack( '.bones[' + bone.name + '].quaternion', times, rotations ) );
-
-				}
+				return new THREE.AnimationClip( 'animation', - 1, tracks );
 
 			}
-
-			return new THREE.AnimationClip( 'animation', - 1, tracks );
-
-		}
-
-		/*
+			/*
 			returns the next non-empty line in lines
 		*/
-		function nextLine( lines ) {
 
-			var line;
-			// skip empty lines
-			while ( ( line = lines.shift().trim() ).length === 0 ) { }
 
-			return line;
+			function nextLine( lines ) {
 
-		}
-
-		var scope = this;
+				var line; // skip empty lines
 
-		var lines = text.split( /[\r\n]+/g );
+				while ( ( line = lines.shift().trim() ).length === 0 ) {}
 
-		var bones = readBvh( lines );
+				return line;
 
-		var threeBones = [];
-		toTHREEBone( bones[ 0 ], threeBones );
+			}
 
-		var threeClip = toTHREEAnimation( bones );
+			var scope = this;
+			var lines = text.split( /[\r\n]+/g );
+			var bones = readBvh( lines );
+			var threeBones = [];
+			toTHREEBone( bones[ 0 ], threeBones );
+			var threeClip = toTHREEAnimation( bones );
+			return {
+				skeleton: new THREE.Skeleton( threeBones ),
+				clip: threeClip
+			};
 
-		return {
-			skeleton: new THREE.Skeleton( threeBones ),
-			clip: threeClip
-		};
+		}
+	} );
 
-	}
+	THREE.BVHLoader = BVHLoader;
 
-} );
+} )();

+ 458 - 499
examples/js/loaders/BasisTextureLoader.js

@@ -1,5 +1,7 @@
-/**
- * Loader for Basis Universal GPU Texture Codec.
+( function () {
+
+	/**
+ * THREE.Loader for Basis Universal GPU Texture Codec.
  *
  * Basis Universal is a "supercompressed" GPU texture and texture video
  * compression system that outputs a highly compressed intermediate file format
@@ -10,167 +12,155 @@
  * of web workers, before transferring the transcoded compressed texture back
  * to the main thread.
  */
-THREE.BasisTextureLoader = function ( manager ) {
-
-	THREE.Loader.call( this, manager );
-
-	this.transcoderPath = '';
-	this.transcoderBinary = null;
-	this.transcoderPending = null;
-
-	this.workerLimit = 4;
-	this.workerPool = [];
-	this.workerNextTaskID = 1;
-	this.workerSourceURL = '';
-	this.workerConfig = null;
-
-};
-
-THREE.BasisTextureLoader.taskCache = new WeakMap();
-
-THREE.BasisTextureLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
-
-	constructor: THREE.BasisTextureLoader,
-
-	setTranscoderPath: function ( path ) {
-
-		this.transcoderPath = path;
 
-		return this;
+	var BasisTextureLoader = function ( manager ) {
 
-	},
+		THREE.Loader.call( this, manager );
+		this.transcoderPath = '';
+		this.transcoderBinary = null;
+		this.transcoderPending = null;
+		this.workerLimit = 4;
+		this.workerPool = [];
+		this.workerNextTaskID = 1;
+		this.workerSourceURL = '';
+		this.workerConfig = null;
 
-	setWorkerLimit: function ( workerLimit ) {
-
-		this.workerLimit = workerLimit;
-
-		return this;
-
-	},
-
-	detectSupport: function ( renderer ) {
-
-		this.workerConfig = {
-			astcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_astc' ),
-			etc1Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc1' ),
-			etc2Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc' ),
-			dxtSupported: renderer.extensions.has( 'WEBGL_compressed_texture_s3tc' ),
-			bptcSupported: renderer.extensions.has( 'EXT_texture_compression_bptc' ),
-			pvrtcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_pvrtc' )
-				|| renderer.extensions.has( 'WEBKIT_WEBGL_compressed_texture_pvrtc' )
-		};
-
-		return this;
+	};
 
-	},
+	BasisTextureLoader.taskCache = new WeakMap();
+	BasisTextureLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+		constructor: BasisTextureLoader,
+		setTranscoderPath: function ( path ) {
 
-	load: function ( url, onLoad, onProgress, onError ) {
+			this.transcoderPath = path;
+			return this;
 
-		var loader = new THREE.FileLoader( this.manager );
+		},
+		setWorkerLimit: function ( workerLimit ) {
 
-		loader.setResponseType( 'arraybuffer' );
-		loader.setWithCredentials( this.withCredentials );
+			this.workerLimit = workerLimit;
+			return this;
 
-		var texture = new THREE.CompressedTexture();
+		},
+		detectSupport: function ( renderer ) {
+
+			this.workerConfig = {
+				astcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_astc' ),
+				etc1Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc1' ),
+				etc2Supported: renderer.extensions.has( 'WEBGL_compressed_texture_etc' ),
+				dxtSupported: renderer.extensions.has( 'WEBGL_compressed_texture_s3tc' ),
+				bptcSupported: renderer.extensions.has( 'EXT_texture_compression_bptc' ),
+				pvrtcSupported: renderer.extensions.has( 'WEBGL_compressed_texture_pvrtc' ) || renderer.extensions.has( 'WEBKIT_WEBGL_compressed_texture_pvrtc' )
+			};
+			return this;
 
-		loader.load( url, ( buffer ) => {
+		},
+		load: function ( url, onLoad, onProgress, onError ) {
 
-			// Check for an existing task using this buffer. A transferred buffer cannot be transferred
-			// again from this thread.
-			if ( THREE.BasisTextureLoader.taskCache.has( buffer ) ) {
+			var loader = new THREE.FileLoader( this.manager );
+			loader.setResponseType( 'arraybuffer' );
+			loader.setWithCredentials( this.withCredentials );
+			var texture = new THREE.CompressedTexture();
+			loader.load( url, buffer => {
 
-				var cachedTask = THREE.BasisTextureLoader.taskCache.get( buffer );
+				// Check for an existing task using this buffer. A transferred buffer cannot be transferred
+				// again from this thread.
+				if ( BasisTextureLoader.taskCache.has( buffer ) ) {
 
-				return cachedTask.promise.then( onLoad ).catch( onError );
+					var cachedTask = BasisTextureLoader.taskCache.get( buffer );
+					return cachedTask.promise.then( onLoad ).catch( onError );
 
-			}
+				}
 
-			this._createTexture( [ buffer ] )
-				.then( function ( _texture ) {
+				this._createTexture( [ buffer ] ).then( function ( _texture ) {
 
 					texture.copy( _texture );
 					texture.needsUpdate = true;
-
 					if ( onLoad ) onLoad( texture );
 
-				} )
-				.catch( onError );
+				} ).catch( onError );
 
-		}, onProgress, onError );
+			}, onProgress, onError );
+			return texture;
 
-		return texture;
-
-	},
-
-	/** Low-level transcoding API, exposed for use by THREE.KTX2Loader. */
-	parseInternalAsync: function ( options ) {
+		},
 
-		var { levels } = options;
+		/** Low-level transcoding API, exposed for use by KTX2Loader. */
+		parseInternalAsync: function ( options ) {
 
-		var buffers = new Set();
+			var {
+				levels
+			} = options;
+			var buffers = new Set();
 
-		for ( var i = 0; i < levels.length; i ++ ) {
+			for ( var i = 0; i < levels.length; i ++ ) {
 
-			buffers.add( levels[ i ].data.buffer );
+				buffers.add( levels[ i ].data.buffer );
 
-		}
+			}
 
-		return this._createTexture( Array.from( buffers ), { ...options, lowLevel: true } );
+			return this._createTexture( Array.from( buffers ), { ...options,
+				lowLevel: true
+			} );
 
-	},
+		},
 
-	/**
+		/**
 	 * @param {ArrayBuffer[]} buffers
 	 * @param {object?} config
-	 * @return {Promise<THREE.CompressedTexture>}
+	 * @return {Promise<CompressedTexture>}
 	 */
-	_createTexture: function ( buffers, config ) {
+		_createTexture: function ( buffers, config ) {
 
-		var worker;
-		var taskID;
+			var worker;
+			var taskID;
+			var taskConfig = config || {};
+			var taskCost = 0;
 
-		var taskConfig = config || {};
-		var taskCost = 0;
+			for ( var i = 0; i < buffers.length; i ++ ) {
 
-		for ( var i = 0; i < buffers.length; i ++ ) {
+				taskCost += buffers[ i ].byteLength;
 
-			taskCost += buffers[ i ].byteLength;
-
-		}
+			}
 
-		var texturePending = this._allocateWorker( taskCost )
-			.then( ( _worker ) => {
+			var texturePending = this._allocateWorker( taskCost ).then( _worker => {
 
 				worker = _worker;
 				taskID = this.workerNextTaskID ++;
-
 				return new Promise( ( resolve, reject ) => {
 
-					worker._callbacks[ taskID ] = { resolve, reject };
-
-					worker.postMessage( { type: 'transcode', id: taskID, buffers: buffers, taskConfig: taskConfig }, buffers );
+					worker._callbacks[ taskID ] = {
+						resolve,
+						reject
+					};
+					worker.postMessage( {
+						type: 'transcode',
+						id: taskID,
+						buffers: buffers,
+						taskConfig: taskConfig
+					}, buffers );
 
 				} );
 
-			} )
-			.then( ( message ) => {
-
-				var { mipmaps, width, height, format } = message;
+			} ).then( message => {
 
+				var {
+					mipmaps,
+					width,
+					height,
+					format
+				} = message;
 				var texture = new THREE.CompressedTexture( mipmaps, width, height, format, THREE.UnsignedByteType );
 				texture.minFilter = mipmaps.length === 1 ? THREE.LinearFilter : THREE.LinearMipmapLinearFilter;
 				texture.magFilter = THREE.LinearFilter;
 				texture.generateMipmaps = false;
 				texture.needsUpdate = true;
-
 				return texture;
 
-			} );
+			} ); // Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416)
 
-		// Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416)
-		texturePending
-			.catch( () => true )
-			.then( () => {
+
+			texturePending.catch( () => true ).then( () => {
 
 				if ( worker && taskID ) {
 
@@ -179,585 +169,554 @@ THREE.BasisTextureLoader.prototype = Object.assign( Object.create( THREE.Loader.
 
 				}
 
-			} );
-
-		// Cache the task result.
-		THREE.BasisTextureLoader.taskCache.set( buffers[ 0 ], { promise: texturePending } );
-
-		return texturePending;
-
-	},
+			} ); // Cache the task result.
 
-	_initTranscoder: function () {
-
-		if ( ! this.transcoderPending ) {
-
-			// Load transcoder wrapper.
-			var jsLoader = new THREE.FileLoader( this.manager );
-			jsLoader.setPath( this.transcoderPath );
-			jsLoader.setWithCredentials( this.withCredentials );
-			var jsContent = new Promise( ( resolve, reject ) => {
+			BasisTextureLoader.taskCache.set( buffers[ 0 ], {
+				promise: texturePending
+			} );
+			return texturePending;
 
-				jsLoader.load( 'basis_transcoder.js', resolve, undefined, reject );
+		},
+		_initTranscoder: function () {
 
-			} );
+			if ( ! this.transcoderPending ) {
 
-			// Load transcoder WASM binary.
-			var binaryLoader = new THREE.FileLoader( this.manager );
-			binaryLoader.setPath( this.transcoderPath );
-			binaryLoader.setResponseType( 'arraybuffer' );
-			binaryLoader.setWithCredentials( this.withCredentials );
-			var binaryContent = new Promise( ( resolve, reject ) => {
+				// Load transcoder wrapper.
+				var jsLoader = new THREE.FileLoader( this.manager );
+				jsLoader.setPath( this.transcoderPath );
+				jsLoader.setWithCredentials( this.withCredentials );
+				var jsContent = new Promise( ( resolve, reject ) => {
 
-				binaryLoader.load( 'basis_transcoder.wasm', resolve, undefined, reject );
+					jsLoader.load( 'basis_transcoder.js', resolve, undefined, reject );
 
-			} );
+				} ); // Load transcoder WASM binary.
 
-			this.transcoderPending = Promise.all( [ jsContent, binaryContent ] )
-				.then( ( [ jsContent, binaryContent ] ) => {
+				var binaryLoader = new THREE.FileLoader( this.manager );
+				binaryLoader.setPath( this.transcoderPath );
+				binaryLoader.setResponseType( 'arraybuffer' );
+				binaryLoader.setWithCredentials( this.withCredentials );
+				var binaryContent = new Promise( ( resolve, reject ) => {
 
-					var fn = THREE.BasisTextureLoader.BasisWorker.toString();
+					binaryLoader.load( 'basis_transcoder.wasm', resolve, undefined, reject );
 
-					var body = [
-						'/* constants */',
-						'var _EngineFormat = ' + JSON.stringify( THREE.BasisTextureLoader.EngineFormat ),
-						'var _TranscoderFormat = ' + JSON.stringify( THREE.BasisTextureLoader.TranscoderFormat ),
-						'var _BasisFormat = ' + JSON.stringify( THREE.BasisTextureLoader.BasisFormat ),
-						'/* basis_transcoder.js */',
-						jsContent,
-						'/* worker */',
-						fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) )
-					].join( '\n' );
+				} );
+				this.transcoderPending = Promise.all( [ jsContent, binaryContent ] ).then( ( [ jsContent, binaryContent ] ) => {
 
+					var fn = BasisTextureLoader.BasisWorker.toString();
+					var body = [ '/* constants */', 'var _EngineFormat = ' + JSON.stringify( BasisTextureLoader.EngineFormat ), 'var _TranscoderFormat = ' + JSON.stringify( BasisTextureLoader.TranscoderFormat ), 'var _BasisFormat = ' + JSON.stringify( BasisTextureLoader.BasisFormat ), '/* basis_transcoder.js */', jsContent, '/* worker */', fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) ].join( '\n' );
 					this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) );
 					this.transcoderBinary = binaryContent;
 
 				} );
 
-		}
-
-		return this.transcoderPending;
-
-	},
+			}
 
-	_allocateWorker: function ( taskCost ) {
+			return this.transcoderPending;
 
-		return this._initTranscoder().then( () => {
+		},
+		_allocateWorker: function ( taskCost ) {
 
-			if ( this.workerPool.length < this.workerLimit ) {
+			return this._initTranscoder().then( () => {
 
-				var worker = new Worker( this.workerSourceURL );
+				if ( this.workerPool.length < this.workerLimit ) {
 
-				worker._callbacks = {};
-				worker._taskLoad = 0;
+					var worker = new Worker( this.workerSourceURL );
+					worker._callbacks = {};
+					worker._taskLoad = 0;
+					worker.postMessage( {
+						type: 'init',
+						config: this.workerConfig,
+						transcoderBinary: this.transcoderBinary
+					} );
 
-				worker.postMessage( {
-					type: 'init',
-					config: this.workerConfig,
-					transcoderBinary: this.transcoderBinary,
-				} );
+					worker.onmessage = function ( e ) {
 
-				worker.onmessage = function ( e ) {
+						var message = e.data;
 
-					var message = e.data;
+						switch ( message.type ) {
 
-					switch ( message.type ) {
+							case 'transcode':
+								worker._callbacks[ message.id ].resolve( message );
 
-						case 'transcode':
-							worker._callbacks[ message.id ].resolve( message );
-							break;
+								break;
 
-						case 'error':
-							worker._callbacks[ message.id ].reject( message );
-							break;
+							case 'error':
+								worker._callbacks[ message.id ].reject( message );
 
-						default:
-							console.error( 'THREE.BasisTextureLoader: Unexpected message, "' + message.type + '"' );
+								break;
 
-					}
+							default:
+								console.error( 'THREE.BasisTextureLoader: Unexpected message, "' + message.type + '"' );
 
-				};
+						}
 
-				this.workerPool.push( worker );
+					};
 
-			} else {
+					this.workerPool.push( worker );
 
-				this.workerPool.sort( function ( a, b ) {
+				} else {
 
-					return a._taskLoad > b._taskLoad ? - 1 : 1;
+					this.workerPool.sort( function ( a, b ) {
 
-				} );
+						return a._taskLoad > b._taskLoad ? - 1 : 1;
 
-			}
+					} );
 
-			var worker = this.workerPool[ this.workerPool.length - 1 ];
+				}
 
-			worker._taskLoad += taskCost;
+				var worker = this.workerPool[ this.workerPool.length - 1 ];
+				worker._taskLoad += taskCost;
+				return worker;
 
-			return worker;
+			} );
 
-		} );
+		},
+		dispose: function () {
 
-	},
+			for ( var i = 0; i < this.workerPool.length; i ++ ) {
 
-	dispose: function () {
+				this.workerPool[ i ].terminate();
 
-		for ( var i = 0; i < this.workerPool.length; i ++ ) {
+			}
 
-			this.workerPool[ i ].terminate();
+			this.workerPool.length = 0;
+			return this;
 
 		}
+	} );
+	/* CONSTANTS */
 
-		this.workerPool.length = 0;
-
-		return this;
-
-	}
-
-} );
-
-/* CONSTANTS */
-
-THREE.BasisTextureLoader.BasisFormat = {
-	ETC1S: 0,
-	UASTC_4x4: 1,
-};
+	BasisTextureLoader.BasisFormat = {
+		ETC1S: 0,
+		UASTC_4x4: 1
+	};
+	BasisTextureLoader.TranscoderFormat = {
+		ETC1: 0,
+		ETC2: 1,
+		BC1: 2,
+		BC3: 3,
+		BC4: 4,
+		BC5: 5,
+		BC7_M6_OPAQUE_ONLY: 6,
+		BC7_M5: 7,
+		PVRTC1_4_RGB: 8,
+		PVRTC1_4_RGBA: 9,
+		ASTC_4x4: 10,
+		ATC_RGB: 11,
+		ATC_RGBA_INTERPOLATED_ALPHA: 12,
+		RGBA32: 13,
+		RGB565: 14,
+		BGR565: 15,
+		RGBA4444: 16
+	};
+	BasisTextureLoader.EngineFormat = {
+		RGBAFormat: THREE.RGBAFormat,
+		RGBA_ASTC_4x4_Format: THREE.RGBA_ASTC_4x4_Format,
+		RGBA_BPTC_Format: THREE.RGBA_BPTC_Format,
+		RGBA_ETC2_EAC_Format: THREE.RGBA_ETC2_EAC_Format,
+		RGBA_PVRTC_4BPPV1_Format: THREE.RGBA_PVRTC_4BPPV1_Format,
+		RGBA_S3TC_DXT5_Format: THREE.RGBA_S3TC_DXT5_Format,
+		RGB_ETC1_Format: THREE.RGB_ETC1_Format,
+		RGB_ETC2_Format: THREE.RGB_ETC2_Format,
+		RGB_PVRTC_4BPPV1_Format: THREE.RGB_PVRTC_4BPPV1_Format,
+		RGB_S3TC_DXT1_Format: THREE.RGB_S3TC_DXT1_Format
+	};
+	/* WEB WORKER */
 
-THREE.BasisTextureLoader.TranscoderFormat = {
-	ETC1: 0,
-	ETC2: 1,
-	BC1: 2,
-	BC3: 3,
-	BC4: 4,
-	BC5: 5,
-	BC7_M6_OPAQUE_ONLY: 6,
-	BC7_M5: 7,
-	PVRTC1_4_RGB: 8,
-	PVRTC1_4_RGBA: 9,
-	ASTC_4x4: 10,
-	ATC_RGB: 11,
-	ATC_RGBA_INTERPOLATED_ALPHA: 12,
-	RGBA32: 13,
-	RGB565: 14,
-	BGR565: 15,
-	RGBA4444: 16,
-};
+	BasisTextureLoader.BasisWorker = function () {
 
-THREE.BasisTextureLoader.EngineFormat = {
-	RGBAFormat: THREE.RGBAFormat,
-	RGBA_ASTC_4x4_Format: THREE.RGBA_ASTC_4x4_Format,
-	RGBA_BPTC_Format: THREE.RGBA_BPTC_Format,
-	RGBA_ETC2_EAC_Format: THREE.RGBA_ETC2_EAC_Format,
-	RGBA_PVRTC_4BPPV1_Format: THREE.RGBA_PVRTC_4BPPV1_Format,
-	RGBA_S3TC_DXT5_Format: THREE.RGBA_S3TC_DXT5_Format,
-	RGB_ETC1_Format: THREE.RGB_ETC1_Format,
-	RGB_ETC2_Format: THREE.RGB_ETC2_Format,
-	RGB_PVRTC_4BPPV1_Format: THREE.RGB_PVRTC_4BPPV1_Format,
-	RGB_S3TC_DXT1_Format: THREE.RGB_S3TC_DXT1_Format,
-};
+		var config;
+		var transcoderPending;
+		var BasisModule;
+		var EngineFormat = _EngineFormat; // eslint-disable-line no-undef
 
+		var TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef
 
-/* WEB WORKER */
+		var BasisFormat = _BasisFormat; // eslint-disable-line no-undef
 
-THREE.BasisTextureLoader.BasisWorker = function () {
+		onmessage = function ( e ) {
 
-	var config;
-	var transcoderPending;
-	var BasisModule;
+			var message = e.data;
 
-	var EngineFormat = _EngineFormat; // eslint-disable-line no-undef
-	var TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef
-	var BasisFormat = _BasisFormat; // eslint-disable-line no-undef
+			switch ( message.type ) {
 
-	onmessage = function ( e ) {
+				case 'init':
+					config = message.config;
+					init( message.transcoderBinary );
+					break;
 
-		var message = e.data;
+				case 'transcode':
+					transcoderPending.then( () => {
 
-		switch ( message.type ) {
+						try {
 
-			case 'init':
-				config = message.config;
-				init( message.transcoderBinary );
-				break;
+							var {
+								width,
+								height,
+								hasAlpha,
+								mipmaps,
+								format
+							} = message.taskConfig.lowLevel ? transcodeLowLevel( message.taskConfig ) : transcode( message.buffers[ 0 ] );
+							var buffers = [];
 
-			case 'transcode':
-				transcoderPending.then( () => {
+							for ( var i = 0; i < mipmaps.length; ++ i ) {
 
-					try {
+								buffers.push( mipmaps[ i ].data.buffer );
 
-						var { width, height, hasAlpha, mipmaps, format } = message.taskConfig.lowLevel
-							? transcodeLowLevel( message.taskConfig )
-							: transcode( message.buffers[ 0 ] );
+							}
 
-						var buffers = [];
+							self.postMessage( {
+								type: 'transcode',
+								id: message.id,
+								width,
+								height,
+								hasAlpha,
+								mipmaps,
+								format
+							}, buffers );
 
-						for ( var i = 0; i < mipmaps.length; ++ i ) {
+						} catch ( error ) {
 
-							buffers.push( mipmaps[ i ].data.buffer );
+							console.error( error );
+							self.postMessage( {
+								type: 'error',
+								id: message.id,
+								error: error.message
+							} );
 
 						}
 
-						self.postMessage( { type: 'transcode', id: message.id, width, height, hasAlpha, mipmaps, format }, buffers );
-
-					} catch ( error ) {
-
-						console.error( error );
-
-						self.postMessage( { type: 'error', id: message.id, error: error.message } );
-
-					}
-
-				} );
-				break;
+					} );
+					break;
 
-		}
-
-	};
-
-	function init( wasmBinary ) {
-
-		transcoderPending = new Promise( ( resolve ) => {
-
-			BasisModule = { wasmBinary, onRuntimeInitialized: resolve };
-			BASIS( BasisModule ); // eslint-disable-line no-undef
-
-		} ).then( () => {
-
-			BasisModule.initializeBasis();
-
-		} );
-
-	}
+			}
 
-	function transcodeLowLevel( taskConfig ) {
+		};
 
-		var { basisFormat, width, height, hasAlpha } = taskConfig;
+		function init( wasmBinary ) {
 
-		var { transcoderFormat, engineFormat } = getTranscoderFormat( basisFormat, width, height, hasAlpha );
+			transcoderPending = new Promise( resolve => {
 
-		var blockByteLength = BasisModule.getBytesPerBlockOrPixel( transcoderFormat );
+				BasisModule = {
+					wasmBinary,
+					onRuntimeInitialized: resolve
+				};
+				BASIS( BasisModule ); // eslint-disable-line no-undef
 
-		assert( BasisModule.isFormatSupported( transcoderFormat ), 'THREE.BasisTextureLoader: Unsupported format.' );
+			} ).then( () => {
 
-		var mipmaps = [];
+				BasisModule.initializeBasis();
 
-		if ( basisFormat === BasisFormat.ETC1S ) {
+			} );
 
-			var transcoder = new BasisModule.LowLevelETC1SImageTranscoder();
+		}
 
-			var { endpointCount, endpointsData, selectorCount, selectorsData, tablesData } = taskConfig.globalData;
+		function transcodeLowLevel( taskConfig ) {
 
-			try {
+			var {
+				basisFormat,
+				width,
+				height,
+				hasAlpha
+			} = taskConfig;
+			var {
+				transcoderFormat,
+				engineFormat
+			} = getTranscoderFormat( basisFormat, width, height, hasAlpha );
+			var blockByteLength = BasisModule.getBytesPerBlockOrPixel( transcoderFormat );
+			assert( BasisModule.isFormatSupported( transcoderFormat ), 'THREE.BasisTextureLoader: Unsupported format.' );
+			var mipmaps = [];
+
+			if ( basisFormat === BasisFormat.ETC1S ) {
+
+				var transcoder = new BasisModule.LowLevelETC1SImageTranscoder();
+				var {
+					endpointCount,
+					endpointsData,
+					selectorCount,
+					selectorsData,
+					tablesData
+				} = taskConfig.globalData;
+
+				try {
+
+					var ok;
+					ok = transcoder.decodePalettes( endpointCount, endpointsData, selectorCount, selectorsData );
+					assert( ok, 'THREE.BasisTextureLoader: decodePalettes() failed.' );
+					ok = transcoder.decodeTables( tablesData );
+					assert( ok, 'THREE.BasisTextureLoader: decodeTables() failed.' );
+
+					for ( var i = 0; i < taskConfig.levels.length; i ++ ) {
+
+						var level = taskConfig.levels[ i ];
+						var imageDesc = taskConfig.globalData.imageDescs[ i ];
+						var dstByteLength = getTranscodedImageByteLength( transcoderFormat, level.width, level.height );
+						var dst = new Uint8Array( dstByteLength );
+						ok = transcoder.transcodeImage( transcoderFormat, dst, dstByteLength / blockByteLength, level.data, getWidthInBlocks( transcoderFormat, level.width ), getHeightInBlocks( transcoderFormat, level.height ), level.width, level.height, level.index, imageDesc.rgbSliceByteOffset, imageDesc.rgbSliceByteLength, imageDesc.alphaSliceByteOffset, imageDesc.alphaSliceByteLength, imageDesc.imageFlags, hasAlpha, false, 0, 0 );
+						assert( ok, 'THREE.BasisTextureLoader: transcodeImage() failed for level ' + level.index + '.' );
+						mipmaps.push( {
+							data: dst,
+							width: level.width,
+							height: level.height
+						} );
 
-				var ok;
+					}
 
-				ok = transcoder.decodePalettes( endpointCount, endpointsData, selectorCount, selectorsData );
+				} finally {
 
-				assert( ok, 'THREE.BasisTextureLoader: decodePalettes() failed.' );
+					transcoder.delete();
 
-				ok = transcoder.decodeTables( tablesData );
+				}
 
-				assert( ok, 'THREE.BasisTextureLoader: decodeTables() failed.' );
+			} else {
 
 				for ( var i = 0; i < taskConfig.levels.length; i ++ ) {
 
 					var level = taskConfig.levels[ i ];
-					var imageDesc = taskConfig.globalData.imageDescs[ i ];
-
 					var dstByteLength = getTranscodedImageByteLength( transcoderFormat, level.width, level.height );
 					var dst = new Uint8Array( dstByteLength );
-
-					ok = transcoder.transcodeImage(
-						transcoderFormat,
-						dst, dstByteLength / blockByteLength,
-						level.data,
-						getWidthInBlocks( transcoderFormat, level.width ),
-						getHeightInBlocks( transcoderFormat, level.height ),
-						level.width, level.height, level.index,
-						imageDesc.rgbSliceByteOffset, imageDesc.rgbSliceByteLength,
-						imageDesc.alphaSliceByteOffset, imageDesc.alphaSliceByteLength,
-						imageDesc.imageFlags,
-						hasAlpha,
-						false,
-						0, 0
-					);
-
-					assert( ok, 'THREE.BasisTextureLoader: transcodeImage() failed for level ' + level.index + '.' );
-
-					mipmaps.push( { data: dst, width: level.width, height: level.height } );
+					var ok = BasisModule.transcodeUASTCImage( transcoderFormat, dst, dstByteLength / blockByteLength, level.data, getWidthInBlocks( transcoderFormat, level.width ), getHeightInBlocks( transcoderFormat, level.height ), level.width, level.height, level.index, 0, level.data.byteLength, 0, hasAlpha, false, 0, 0, - 1, - 1 );
+					assert( ok, 'THREE.BasisTextureLoader: transcodeUASTCImage() failed for level ' + level.index + '.' );
+					mipmaps.push( {
+						data: dst,
+						width: level.width,
+						height: level.height
+					} );
 
 				}
 
-			} finally {
-
-				transcoder.delete();
-
 			}
 
-		} else {
-
-			for ( var i = 0; i < taskConfig.levels.length; i ++ ) {
-
-				var level = taskConfig.levels[ i ];
-
-				var dstByteLength = getTranscodedImageByteLength( transcoderFormat, level.width, level.height );
-				var dst = new Uint8Array( dstByteLength );
-
-				var ok = BasisModule.transcodeUASTCImage(
-					transcoderFormat,
-					dst, dstByteLength / blockByteLength,
-					level.data,
-					getWidthInBlocks( transcoderFormat, level.width ),
-					getHeightInBlocks( transcoderFormat, level.height ),
-					level.width, level.height, level.index,
-					0,
-					level.data.byteLength,
-					0,
-					hasAlpha,
-					false,
-					0, 0,
-					- 1, - 1
-				);
-
-				assert( ok, 'THREE.BasisTextureLoader: transcodeUASTCImage() failed for level ' + level.index + '.' );
-
-				mipmaps.push( { data: dst, width: level.width, height: level.height } );
-
-			}
+			return {
+				width,
+				height,
+				hasAlpha,
+				mipmaps,
+				format: engineFormat
+			};
 
 		}
 
-		return { width, height, hasAlpha, mipmaps, format: engineFormat };
-
-	}
+		function transcode( buffer ) {
 
-	function transcode( buffer ) {
+			var basisFile = new BasisModule.BasisFile( new Uint8Array( buffer ) );
+			var basisFormat = basisFile.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S;
+			var width = basisFile.getImageWidth( 0, 0 );
+			var height = basisFile.getImageHeight( 0, 0 );
+			var levels = basisFile.getNumLevels( 0 );
+			var hasAlpha = basisFile.getHasAlpha();
 
-		var basisFile = new BasisModule.BasisFile( new Uint8Array( buffer ) );
+			function cleanup() {
 
-		var basisFormat = basisFile.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S;
-		var width = basisFile.getImageWidth( 0, 0 );
-		var height = basisFile.getImageHeight( 0, 0 );
-		var levels = basisFile.getNumLevels( 0 );
-		var hasAlpha = basisFile.getHasAlpha();
+				basisFile.close();
+				basisFile.delete();
 
-		function cleanup() {
-
-			basisFile.close();
-			basisFile.delete();
+			}
 
-		}
+			var {
+				transcoderFormat,
+				engineFormat
+			} = getTranscoderFormat( basisFormat, width, height, hasAlpha );
 
-		var { transcoderFormat, engineFormat } = getTranscoderFormat( basisFormat, width, height, hasAlpha );
+			if ( ! width || ! height || ! levels ) {
 
-		if ( ! width || ! height || ! levels ) {
+				cleanup();
+				throw new Error( 'THREE.BasisTextureLoader:	Invalid texture' );
 
-			cleanup();
-			throw new Error( 'THREE.BasisTextureLoader:	Invalid texture' );
+			}
 
-		}
+			if ( ! basisFile.startTranscoding() ) {
 
-		if ( ! basisFile.startTranscoding() ) {
+				cleanup();
+				throw new Error( 'THREE.BasisTextureLoader: .startTranscoding failed' );
 
-			cleanup();
-			throw new Error( 'THREE.BasisTextureLoader: .startTranscoding failed' );
+			}
 
-		}
+			var mipmaps = [];
 
-		var mipmaps = [];
+			for ( var mip = 0; mip < levels; mip ++ ) {
 
-		for ( var mip = 0; mip < levels; mip ++ ) {
+				var mipWidth = basisFile.getImageWidth( 0, mip );
+				var mipHeight = basisFile.getImageHeight( 0, mip );
+				var dst = new Uint8Array( basisFile.getImageTranscodedSizeInBytes( 0, mip, transcoderFormat ) );
+				var status = basisFile.transcodeImage( dst, 0, mip, transcoderFormat, 0, hasAlpha );
 
-			var mipWidth = basisFile.getImageWidth( 0, mip );
-			var mipHeight = basisFile.getImageHeight( 0, mip );
-			var dst = new Uint8Array( basisFile.getImageTranscodedSizeInBytes( 0, mip, transcoderFormat ) );
+				if ( ! status ) {
 
-			var status = basisFile.transcodeImage(
-				dst,
-				0,
-				mip,
-				transcoderFormat,
-				0,
-				hasAlpha
-			);
+					cleanup();
+					throw new Error( 'THREE.BasisTextureLoader: .transcodeImage failed.' );
 
-			if ( ! status ) {
+				}
 
-				cleanup();
-				throw new Error( 'THREE.BasisTextureLoader: .transcodeImage failed.' );
+				mipmaps.push( {
+					data: dst,
+					width: mipWidth,
+					height: mipHeight
+				} );
 
 			}
 
-			mipmaps.push( { data: dst, width: mipWidth, height: mipHeight } );
-
-		}
-
-		cleanup();
-
-		return { width, height, hasAlpha, mipmaps, format: engineFormat };
-
-	}
-
-	//
-
-	// Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC),
-	// device capabilities, and texture dimensions. The list below ranks the formats separately
-	// for ETC1S and UASTC.
-	//
-	// In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at
-	// significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently
-	// chooses RGBA32 only as a last resort and does not expose that option to the caller.
-	var FORMAT_OPTIONS = [
-		{
+			cleanup();
+			return {
+				width,
+				height,
+				hasAlpha,
+				mipmaps,
+				format: engineFormat
+			};
+
+		} //
+		// Optimal choice of a transcoder target format depends on the Basis format (ETC1S or UASTC),
+		// device capabilities, and texture dimensions. The list below ranks the formats separately
+		// for ETC1S and UASTC.
+		//
+		// In some cases, transcoding UASTC to RGBA32 might be preferred for higher quality (at
+		// significant memory cost) compared to ETC1/2, BC1/3, and PVRTC. The transcoder currently
+		// chooses RGBA32 only as a last resort and does not expose that option to the caller.
+
+
+		var FORMAT_OPTIONS = [ {
 			if: 'astcSupported',
 			basisFormat: [ BasisFormat.UASTC_4x4 ],
 			transcoderFormat: [ TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4 ],
 			engineFormat: [ EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format ],
 			priorityETC1S: Infinity,
 			priorityUASTC: 1,
-			needsPowerOfTwo: false,
-		},
-		{
+			needsPowerOfTwo: false
+		}, {
 			if: 'bptcSupported',
 			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
 			transcoderFormat: [ TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5 ],
 			engineFormat: [ EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format ],
 			priorityETC1S: 3,
 			priorityUASTC: 2,
-			needsPowerOfTwo: false,
-		},
-		{
+			needsPowerOfTwo: false
+		}, {
 			if: 'dxtSupported',
 			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
 			transcoderFormat: [ TranscoderFormat.BC1, TranscoderFormat.BC3 ],
 			engineFormat: [ EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format ],
 			priorityETC1S: 4,
 			priorityUASTC: 5,
-			needsPowerOfTwo: false,
-		},
-		{
+			needsPowerOfTwo: false
+		}, {
 			if: 'etc2Supported',
 			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
 			transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC2 ],
 			engineFormat: [ EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format ],
 			priorityETC1S: 1,
 			priorityUASTC: 3,
-			needsPowerOfTwo: false,
-		},
-		{
+			needsPowerOfTwo: false
+		}, {
 			if: 'etc1Supported',
 			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
 			transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC1 ],
 			engineFormat: [ EngineFormat.RGB_ETC1_Format, EngineFormat.RGB_ETC1_Format ],
 			priorityETC1S: 2,
 			priorityUASTC: 4,
-			needsPowerOfTwo: false,
-		},
-		{
+			needsPowerOfTwo: false
+		}, {
 			if: 'pvrtcSupported',
 			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
 			transcoderFormat: [ TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA ],
 			engineFormat: [ EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format ],
 			priorityETC1S: 5,
 			priorityUASTC: 6,
-			needsPowerOfTwo: true,
-		},
-	];
+			needsPowerOfTwo: true
+		} ];
+		var ETC1S_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) {
 
-	var ETC1S_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) {
+			return a.priorityETC1S - b.priorityETC1S;
 
-		return a.priorityETC1S - b.priorityETC1S;
+		} );
+		var UASTC_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) {
 
-	} );
-	var UASTC_OPTIONS = FORMAT_OPTIONS.sort( function ( a, b ) {
+			return a.priorityUASTC - b.priorityUASTC;
 
-		return a.priorityUASTC - b.priorityUASTC;
+		} );
 
-	} );
+		function getTranscoderFormat( basisFormat, width, height, hasAlpha ) {
 
-	function getTranscoderFormat( basisFormat, width, height, hasAlpha ) {
+			var transcoderFormat;
+			var engineFormat;
+			var options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS;
 
-		var transcoderFormat;
-		var engineFormat;
+			for ( var i = 0; i < options.length; i ++ ) {
 
-		var options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS;
+				var opt = options[ i ];
+				if ( ! config[ opt.if ] ) continue;
+				if ( ! opt.basisFormat.includes( basisFormat ) ) continue;
+				if ( opt.needsPowerOfTwo && ! ( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) continue;
+				transcoderFormat = opt.transcoderFormat[ hasAlpha ? 1 : 0 ];
+				engineFormat = opt.engineFormat[ hasAlpha ? 1 : 0 ];
+				return {
+					transcoderFormat,
+					engineFormat
+				};
 
-		for ( var i = 0; i < options.length; i ++ ) {
+			}
 
-			var opt = options[ i ];
+			console.warn( 'THREE.BasisTextureLoader: No suitable compressed texture format found. Decoding to RGBA32.' );
+			transcoderFormat = TranscoderFormat.RGBA32;
+			engineFormat = EngineFormat.RGBAFormat;
+			return {
+				transcoderFormat,
+				engineFormat
+			};
 
-			if ( ! config[ opt.if ] ) continue;
-			if ( ! opt.basisFormat.includes( basisFormat ) ) continue;
-			if ( opt.needsPowerOfTwo && ! ( isPowerOfTwo( width ) && isPowerOfTwo( height ) ) ) continue;
+		}
 
-			transcoderFormat = opt.transcoderFormat[ hasAlpha ? 1 : 0 ];
-			engineFormat = opt.engineFormat[ hasAlpha ? 1 : 0 ];
+		function assert( ok, message ) {
 
-			return { transcoderFormat, engineFormat };
+			if ( ! ok ) throw new Error( message );
 
 		}
 
-		console.warn( 'THREE.BasisTextureLoader: No suitable compressed texture format found. Decoding to RGBA32.' );
-
-		transcoderFormat = TranscoderFormat.RGBA32;
-		engineFormat = EngineFormat.RGBAFormat;
-
-		return { transcoderFormat, engineFormat };
+		function getWidthInBlocks( transcoderFormat, width ) {
 
-	}
+			return Math.ceil( width / BasisModule.getFormatBlockWidth( transcoderFormat ) );
 
-	function assert( ok, message ) {
+		}
 
-		if ( ! ok ) throw new Error( message );
+		function getHeightInBlocks( transcoderFormat, height ) {
 
-	}
+			return Math.ceil( height / BasisModule.getFormatBlockHeight( transcoderFormat ) );
 
-	function getWidthInBlocks( transcoderFormat, width ) {
+		}
 
-		return Math.ceil( width / BasisModule.getFormatBlockWidth( transcoderFormat ) );
+		function getTranscodedImageByteLength( transcoderFormat, width, height ) {
 
-	}
+			var blockByteLength = BasisModule.getBytesPerBlockOrPixel( transcoderFormat );
 
-	function getHeightInBlocks( transcoderFormat, height ) {
+			if ( BasisModule.formatIsUncompressed( transcoderFormat ) ) {
 
-		return Math.ceil( height / BasisModule.getFormatBlockHeight( transcoderFormat ) );
+				return width * height * blockByteLength;
 
-	}
+			}
 
-	function getTranscodedImageByteLength( transcoderFormat, width, height ) {
+			if ( transcoderFormat === TranscoderFormat.PVRTC1_4_RGB || transcoderFormat === TranscoderFormat.PVRTC1_4_RGBA ) {
 
-		var blockByteLength = BasisModule.getBytesPerBlockOrPixel( transcoderFormat );
+				// GL requires extra padding for very small textures:
+				// https://www.khronos.org/registry/OpenGL/extensions/IMG/IMG_texture_compression_pvrtc.txt
+				var paddedWidth = width + 3 & ~ 3;
+				var paddedHeight = height + 3 & ~ 3;
+				return ( Math.max( 8, paddedWidth ) * Math.max( 8, paddedHeight ) * 4 + 7 ) / 8;
 
-		if ( BasisModule.formatIsUncompressed( transcoderFormat ) ) {
+			}
 
-			return width * height * blockByteLength;
+			return getWidthInBlocks( transcoderFormat, width ) * getHeightInBlocks( transcoderFormat, height ) * blockByteLength;
 
 		}
 
-		if ( transcoderFormat === TranscoderFormat.PVRTC1_4_RGB
-				|| transcoderFormat === TranscoderFormat.PVRTC1_4_RGBA ) {
-
-			// GL requires extra padding for very small textures:
-			// https://www.khronos.org/registry/OpenGL/extensions/IMG/IMG_texture_compression_pvrtc.txt
-			var paddedWidth = ( width + 3 ) & ~ 3;
-			var paddedHeight = ( height + 3 ) & ~ 3;
+		function isPowerOfTwo( value ) {
 
-			return ( Math.max( 8, paddedWidth ) * Math.max( 8, paddedHeight ) * 4 + 7 ) / 8;
+			if ( value <= 2 ) return true;
+			return ( value & value - 1 ) === 0 && value !== 0;
 
 		}
 
-		return ( getWidthInBlocks( transcoderFormat, width )
-			* getHeightInBlocks( transcoderFormat, height )
-			* blockByteLength );
-
-	}
-
-	function isPowerOfTwo( value ) {
-
-		if ( value <= 2 ) return true;
-
-		return ( value & ( value - 1 ) ) === 0 && value !== 0;
+	};
 
-	}
+	THREE.BasisTextureLoader = BasisTextureLoader;
 
-};
+} )();

+ 2277 - 2502
examples/js/loaders/ColladaLoader.js

@@ -1,3982 +1,3757 @@
-THREE.ColladaLoader = function ( manager ) {
+( function () {
 
-	THREE.Loader.call( this, manager );
+	var ColladaLoader = function ( manager ) {
 
-};
+		THREE.Loader.call( this, manager );
 
-THREE.ColladaLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+	};
 
-	constructor: THREE.ColladaLoader,
+	ColladaLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+		constructor: ColladaLoader,
+		load: function ( url, onLoad, onProgress, onError ) {
 
-	load: function ( url, onLoad, onProgress, onError ) {
+			var scope = this;
+			var path = scope.path === '' ? THREE.LoaderUtils.extractUrlBase( url ) : scope.path;
+			var loader = new THREE.FileLoader( scope.manager );
+			loader.setPath( scope.path );
+			loader.setRequestHeader( scope.requestHeader );
+			loader.setWithCredentials( scope.withCredentials );
+			loader.load( url, function ( text ) {
 
-		var scope = this;
+				try {
 
-		var path = ( scope.path === '' ) ? THREE.LoaderUtils.extractUrlBase( url ) : scope.path;
+					onLoad( scope.parse( text, path ) );
 
-		var loader = new THREE.FileLoader( scope.manager );
-		loader.setPath( scope.path );
-		loader.setRequestHeader( scope.requestHeader );
-		loader.setWithCredentials( scope.withCredentials );
-		loader.load( url, function ( text ) {
+				} catch ( e ) {
 
-			try {
+					if ( onError ) {
 
-				onLoad( scope.parse( text, path ) );
+						onError( e );
 
-			} catch ( e ) {
-
-				if ( onError ) {
+					} else {
 
-					onError( e );
+						console.error( e );
 
-				} else {
+					}
 
-					console.error( e );
+					scope.manager.itemError( url );
 
 				}
 
-				scope.manager.itemError( url );
+			}, onProgress, onError );
+
+		},
+		options: {
+			set convertUpAxis( value ) {
+
+				console.warn( 'THREE.ColladaLoader: options.convertUpAxis() has been removed. Up axis is converted automatically.' );
 
 			}
 
-		}, onProgress, onError );
+		},
+		parse: function ( text, path ) {
 
-	},
+			function getElementsByTagName( xml, name ) {
 
-	options: {
+				// Non recursive xml.getElementsByTagName() ...
+				var array = [];
+				var childNodes = xml.childNodes;
 
-		set convertUpAxis( value ) {
+				for ( var i = 0, l = childNodes.length; i < l; i ++ ) {
 
-			console.warn( 'THREE.ColladaLoader: options.convertUpAxis() has been removed. Up axis is converted automatically.' );
+					var child = childNodes[ i ];
 
-		}
+					if ( child.nodeName === name ) {
 
-	},
+						array.push( child );
 
-	parse: function ( text, path ) {
+					}
 
-		function getElementsByTagName( xml, name ) {
+				}
 
-			// Non recursive xml.getElementsByTagName() ...
+				return array;
 
-			var array = [];
-			var childNodes = xml.childNodes;
+			}
 
-			for ( var i = 0, l = childNodes.length; i < l; i ++ ) {
+			function parseStrings( text ) {
 
-				var child = childNodes[ i ];
+				if ( text.length === 0 ) return [];
+				var parts = text.trim().split( /\s+/ );
+				var array = new Array( parts.length );
 
-				if ( child.nodeName === name ) {
+				for ( var i = 0, l = parts.length; i < l; i ++ ) {
 
-					array.push( child );
+					array[ i ] = parts[ i ];
 
 				}
 
-			}
+				return array;
 
-			return array;
+			}
 
-		}
+			function parseFloats( text ) {
 
-		function parseStrings( text ) {
+				if ( text.length === 0 ) return [];
+				var parts = text.trim().split( /\s+/ );
+				var array = new Array( parts.length );
 
-			if ( text.length === 0 ) return [];
+				for ( var i = 0, l = parts.length; i < l; i ++ ) {
 
-			var parts = text.trim().split( /\s+/ );
-			var array = new Array( parts.length );
+					array[ i ] = parseFloat( parts[ i ] );
 
-			for ( var i = 0, l = parts.length; i < l; i ++ ) {
+				}
 
-				array[ i ] = parts[ i ];
+				return array;
 
 			}
 
-			return array;
-
-		}
+			function parseInts( text ) {
 
-		function parseFloats( text ) {
+				if ( text.length === 0 ) return [];
+				var parts = text.trim().split( /\s+/ );
+				var array = new Array( parts.length );
 
-			if ( text.length === 0 ) return [];
+				for ( var i = 0, l = parts.length; i < l; i ++ ) {
 
-			var parts = text.trim().split( /\s+/ );
-			var array = new Array( parts.length );
+					array[ i ] = parseInt( parts[ i ] );
 
-			for ( var i = 0, l = parts.length; i < l; i ++ ) {
+				}
 
-				array[ i ] = parseFloat( parts[ i ] );
+				return array;
 
 			}
 
-			return array;
-
-		}
-
-		function parseInts( text ) {
+			function parseId( text ) {
 
-			if ( text.length === 0 ) return [];
+				return text.substring( 1 );
 
-			var parts = text.trim().split( /\s+/ );
-			var array = new Array( parts.length );
+			}
 
-			for ( var i = 0, l = parts.length; i < l; i ++ ) {
+			function generateId() {
 
-				array[ i ] = parseInt( parts[ i ] );
+				return 'three_default_' + count ++;
 
 			}
 
-			return array;
+			function isEmpty( object ) {
 
-		}
+				return Object.keys( object ).length === 0;
 
-		function parseId( text ) {
+			} // asset
 
-			return text.substring( 1 );
 
-		}
+			function parseAsset( xml ) {
 
-		function generateId() {
+				return {
+					unit: parseAssetUnit( getElementsByTagName( xml, 'unit' )[ 0 ] ),
+					upAxis: parseAssetUpAxis( getElementsByTagName( xml, 'up_axis' )[ 0 ] )
+				};
 
-			return 'three_default_' + ( count ++ );
+			}
 
-		}
+			function parseAssetUnit( xml ) {
 
-		function isEmpty( object ) {
+				if ( xml !== undefined && xml.hasAttribute( 'meter' ) === true ) {
 
-			return Object.keys( object ).length === 0;
+					return parseFloat( xml.getAttribute( 'meter' ) );
 
-		}
+				} else {
 
-		// asset
+					return 1; // default 1 meter
 
-		function parseAsset( xml ) {
+				}
 
-			return {
-				unit: parseAssetUnit( getElementsByTagName( xml, 'unit' )[ 0 ] ),
-				upAxis: parseAssetUpAxis( getElementsByTagName( xml, 'up_axis' )[ 0 ] )
-			};
+			}
 
-		}
+			function parseAssetUpAxis( xml ) {
 
-		function parseAssetUnit( xml ) {
+				return xml !== undefined ? xml.textContent : 'Y_UP';
 
-			if ( ( xml !== undefined ) && ( xml.hasAttribute( 'meter' ) === true ) ) {
+			} // library
 
-				return parseFloat( xml.getAttribute( 'meter' ) );
 
-			} else {
+			function parseLibrary( xml, libraryName, nodeName, parser ) {
 
-				return 1; // default 1 meter
+				var library = getElementsByTagName( xml, libraryName )[ 0 ];
 
-			}
+				if ( library !== undefined ) {
 
-		}
+					var elements = getElementsByTagName( library, nodeName );
 
-		function parseAssetUpAxis( xml ) {
+					for ( var i = 0; i < elements.length; i ++ ) {
 
-			return xml !== undefined ? xml.textContent : 'Y_UP';
+						parser( elements[ i ] );
 
-		}
+					}
 
-		// library
+				}
 
-		function parseLibrary( xml, libraryName, nodeName, parser ) {
+			}
 
-			var library = getElementsByTagName( xml, libraryName )[ 0 ];
+			function buildLibrary( data, builder ) {
 
-			if ( library !== undefined ) {
+				for ( var name in data ) {
 
-				var elements = getElementsByTagName( library, nodeName );
+					var object = data[ name ];
+					object.build = builder( data[ name ] );
 
-				for ( var i = 0; i < elements.length; i ++ ) {
+				}
 
-					parser( elements[ i ] );
+			} // get
 
-				}
 
-			}
+			function getBuild( data, builder ) {
 
-		}
+				if ( data.build !== undefined ) return data.build;
+				data.build = builder( data );
+				return data.build;
 
-		function buildLibrary( data, builder ) {
+			} // animation
 
-			for ( var name in data ) {
 
-				var object = data[ name ];
-				object.build = builder( data[ name ] );
+			function parseAnimation( xml ) {
 
-			}
+				var data = {
+					sources: {},
+					samplers: {},
+					channels: {}
+				};
+				var hasChildren = false;
 
-		}
+				for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
-		// get
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
+					var id;
 
-		function getBuild( data, builder ) {
+					switch ( child.nodeName ) {
 
-			if ( data.build !== undefined ) return data.build;
+						case 'source':
+							id = child.getAttribute( 'id' );
+							data.sources[ id ] = parseSource( child );
+							break;
 
-			data.build = builder( data );
+						case 'sampler':
+							id = child.getAttribute( 'id' );
+							data.samplers[ id ] = parseAnimationSampler( child );
+							break;
 
-			return data.build;
+						case 'channel':
+							id = child.getAttribute( 'target' );
+							data.channels[ id ] = parseAnimationChannel( child );
+							break;
 
-		}
+						case 'animation':
+						// hierarchy of related animations
+							parseAnimation( child );
+							hasChildren = true;
+							break;
 
-		// animation
+						default:
+							console.log( child );
 
-		function parseAnimation( xml ) {
+					}
 
-			var data = {
-				sources: {},
-				samplers: {},
-				channels: {}
-			};
+				}
 
-			var hasChildren = false;
+				if ( hasChildren === false ) {
 
-			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+					// since 'id' attributes can be optional, it's necessary to generate a UUID for unqiue assignment
+					library.animations[ xml.getAttribute( 'id' ) || THREE.MathUtils.generateUUID() ] = data;
 
-				var child = xml.childNodes[ i ];
+				}
 
-				if ( child.nodeType !== 1 ) continue;
+			}
 
-				var id;
+			function parseAnimationSampler( xml ) {
 
-				switch ( child.nodeName ) {
+				var data = {
+					inputs: {}
+				};
 
-					case 'source':
-						id = child.getAttribute( 'id' );
-						data.sources[ id ] = parseSource( child );
-						break;
+				for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
-					case 'sampler':
-						id = child.getAttribute( 'id' );
-						data.samplers[ id ] = parseAnimationSampler( child );
-						break;
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-					case 'channel':
-						id = child.getAttribute( 'target' );
-						data.channels[ id ] = parseAnimationChannel( child );
-						break;
+					switch ( child.nodeName ) {
 
-					case 'animation':
-						// hierarchy of related animations
-						parseAnimation( child );
-						hasChildren = true;
-						break;
+						case 'input':
+							var id = parseId( child.getAttribute( 'source' ) );
+							var semantic = child.getAttribute( 'semantic' );
+							data.inputs[ semantic ] = id;
+							break;
 
-					default:
-						console.log( child );
+					}
 
 				}
 
+				return data;
+
 			}
 
-			if ( hasChildren === false ) {
+			function parseAnimationChannel( xml ) {
 
-				// since 'id' attributes can be optional, it's necessary to generate a UUID for unqiue assignment
+				var data = {};
+				var target = xml.getAttribute( 'target' ); // parsing SID Addressing Syntax
 
-				library.animations[ xml.getAttribute( 'id' ) || THREE.MathUtils.generateUUID() ] = data;
+				var parts = target.split( '/' );
+				var id = parts.shift();
+				var sid = parts.shift(); // check selection syntax
 
-			}
+				var arraySyntax = sid.indexOf( '(' ) !== - 1;
+				var memberSyntax = sid.indexOf( '.' ) !== - 1;
 
-		}
+				if ( memberSyntax ) {
 
-		function parseAnimationSampler( xml ) {
+					//	member selection access
+					parts = sid.split( '.' );
+					sid = parts.shift();
+					data.member = parts.shift();
 
-			var data = {
-				inputs: {},
-			};
+				} else if ( arraySyntax ) {
 
-			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+					// array-access syntax. can be used to express fields in one-dimensional vectors or two-dimensional matrices.
+					var indices = sid.split( '(' );
+					sid = indices.shift();
 
-				var child = xml.childNodes[ i ];
+					for ( var i = 0; i < indices.length; i ++ ) {
 
-				if ( child.nodeType !== 1 ) continue;
+						indices[ i ] = parseInt( indices[ i ].replace( /\)/, '' ) );
 
-				switch ( child.nodeName ) {
+					}
 
-					case 'input':
-						var id = parseId( child.getAttribute( 'source' ) );
-						var semantic = child.getAttribute( 'semantic' );
-						data.inputs[ semantic ] = id;
-						break;
+					data.indices = indices;
 
 				}
 
+				data.id = id;
+				data.sid = sid;
+				data.arraySyntax = arraySyntax;
+				data.memberSyntax = memberSyntax;
+				data.sampler = parseId( xml.getAttribute( 'source' ) );
+				return data;
+
 			}
 
-			return data;
+			function buildAnimation( data ) {
 
-		}
+				var tracks = [];
+				var channels = data.channels;
+				var samplers = data.samplers;
+				var sources = data.sources;
 
-		function parseAnimationChannel( xml ) {
+				for ( var target in channels ) {
 
-			var data = {};
+					if ( channels.hasOwnProperty( target ) ) {
 
-			var target = xml.getAttribute( 'target' );
+						var channel = channels[ target ];
+						var sampler = samplers[ channel.sampler ];
+						var inputId = sampler.inputs.INPUT;
+						var outputId = sampler.inputs.OUTPUT;
+						var inputSource = sources[ inputId ];
+						var outputSource = sources[ outputId ];
+						var animation = buildAnimationChannel( channel, inputSource, outputSource );
+						createKeyframeTracks( animation, tracks );
 
-			// parsing SID Addressing Syntax
+					}
+
+				}
 
-			var parts = target.split( '/' );
+				return tracks;
 
-			var id = parts.shift();
-			var sid = parts.shift();
+			}
 
-			// check selection syntax
+			function getAnimation( id ) {
 
-			var arraySyntax = ( sid.indexOf( '(' ) !== - 1 );
-			var memberSyntax = ( sid.indexOf( '.' ) !== - 1 );
+				return getBuild( library.animations[ id ], buildAnimation );
 
-			if ( memberSyntax ) {
+			}
 
-				//  member selection access
+			function buildAnimationChannel( channel, inputSource, outputSource ) {
 
-				parts = sid.split( '.' );
-				sid = parts.shift();
-				data.member = parts.shift();
+				var node = library.nodes[ channel.id ];
+				var object3D = getNode( node.id );
+				var transform = node.transforms[ channel.sid ];
+				var defaultMatrix = node.matrix.clone().transpose();
+				var time, stride;
+				var i, il, j, jl;
+				var data = {}; // the collada spec allows the animation of data in various ways.
+				// depending on the transform type (matrix, translate, rotate, scale), we execute different logic
 
-			} else if ( arraySyntax ) {
+				switch ( transform ) {
 
-				// array-access syntax. can be used to express fields in one-dimensional vectors or two-dimensional matrices.
+					case 'matrix':
+						for ( i = 0, il = inputSource.array.length; i < il; i ++ ) {
 
-				var indices = sid.split( '(' );
-				sid = indices.shift();
+							time = inputSource.array[ i ];
+							stride = i * outputSource.stride;
+							if ( data[ time ] === undefined ) data[ time ] = {};
 
-				for ( var i = 0; i < indices.length; i ++ ) {
+							if ( channel.arraySyntax === true ) {
 
-					indices[ i ] = parseInt( indices[ i ].replace( /\)/, '' ) );
+								var value = outputSource.array[ stride ];
+								var index = channel.indices[ 0 ] + 4 * channel.indices[ 1 ];
+								data[ time ][ index ] = value;
 
-				}
+							} else {
 
-				data.indices = indices;
+								for ( j = 0, jl = outputSource.stride; j < jl; j ++ ) {
 
-			}
+									data[ time ][ j ] = outputSource.array[ stride + j ];
 
-			data.id = id;
-			data.sid = sid;
+								}
 
-			data.arraySyntax = arraySyntax;
-			data.memberSyntax = memberSyntax;
+							}
 
-			data.sampler = parseId( xml.getAttribute( 'source' ) );
+						}
 
-			return data;
+						break;
 
-		}
+					case 'translate':
+						console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform );
+						break;
 
-		function buildAnimation( data ) {
+					case 'rotate':
+						console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform );
+						break;
 
-			var tracks = [];
+					case 'scale':
+						console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform );
+						break;
 
-			var channels = data.channels;
-			var samplers = data.samplers;
-			var sources = data.sources;
+				}
 
-			for ( var target in channels ) {
+				var keyframes = prepareAnimationData( data, defaultMatrix );
+				var animation = {
+					name: object3D.uuid,
+					keyframes: keyframes
+				};
+				return animation;
 
-				if ( channels.hasOwnProperty( target ) ) {
+			}
 
-					var channel = channels[ target ];
-					var sampler = samplers[ channel.sampler ];
+			function prepareAnimationData( data, defaultMatrix ) {
 
-					var inputId = sampler.inputs.INPUT;
-					var outputId = sampler.inputs.OUTPUT;
+				var keyframes = []; // transfer data into a sortable array
 
-					var inputSource = sources[ inputId ];
-					var outputSource = sources[ outputId ];
+				for ( var time in data ) {
 
-					var animation = buildAnimationChannel( channel, inputSource, outputSource );
+					keyframes.push( {
+						time: parseFloat( time ),
+						value: data[ time ]
+					} );
 
-					createKeyframeTracks( animation, tracks );
+				} // ensure keyframes are sorted by time
 
-				}
 
-			}
+				keyframes.sort( ascending ); // now we clean up all animation data, so we can use them for keyframe tracks
 
-			return tracks;
+				for ( var i = 0; i < 16; i ++ ) {
 
-		}
+					transformAnimationData( keyframes, i, defaultMatrix.elements[ i ] );
 
-		function getAnimation( id ) {
+				}
 
-			return getBuild( library.animations[ id ], buildAnimation );
+				return keyframes; // array sort function
 
-		}
+				function ascending( a, b ) {
 
-		function buildAnimationChannel( channel, inputSource, outputSource ) {
+					return a.time - b.time;
 
-			var node = library.nodes[ channel.id ];
-			var object3D = getNode( node.id );
+				}
 
-			var transform = node.transforms[ channel.sid ];
-			var defaultMatrix = node.matrix.clone().transpose();
+			}
 
-			var time, stride;
-			var i, il, j, jl;
+			var position = new THREE.Vector3();
+			var scale = new THREE.Vector3();
+			var quaternion = new THREE.Quaternion();
 
-			var data = {};
+			function createKeyframeTracks( animation, tracks ) {
 
-			// the collada spec allows the animation of data in various ways.
-			// depending on the transform type (matrix, translate, rotate, scale), we execute different logic
+				var keyframes = animation.keyframes;
+				var name = animation.name;
+				var times = [];
+				var positionData = [];
+				var quaternionData = [];
+				var scaleData = [];
 
-			switch ( transform ) {
+				for ( var i = 0, l = keyframes.length; i < l; i ++ ) {
 
-				case 'matrix':
+					var keyframe = keyframes[ i ];
+					var time = keyframe.time;
+					var value = keyframe.value;
+					matrix.fromArray( value ).transpose();
+					matrix.decompose( position, quaternion, scale );
+					times.push( time );
+					positionData.push( position.x, position.y, position.z );
+					quaternionData.push( quaternion.x, quaternion.y, quaternion.z, quaternion.w );
+					scaleData.push( scale.x, scale.y, scale.z );
 
-					for ( i = 0, il = inputSource.array.length; i < il; i ++ ) {
+				}
 
-						time = inputSource.array[ i ];
-						stride = i * outputSource.stride;
+				if ( positionData.length > 0 ) tracks.push( new THREE.VectorKeyframeTrack( name + '.position', times, positionData ) );
+				if ( quaternionData.length > 0 ) tracks.push( new THREE.QuaternionKeyframeTrack( name + '.quaternion', times, quaternionData ) );
+				if ( scaleData.length > 0 ) tracks.push( new THREE.VectorKeyframeTrack( name + '.scale', times, scaleData ) );
+				return tracks;
 
-						if ( data[ time ] === undefined ) data[ time ] = {};
+			}
 
-						if ( channel.arraySyntax === true ) {
+			function transformAnimationData( keyframes, property, defaultValue ) {
 
-							var value = outputSource.array[ stride ];
-							var index = channel.indices[ 0 ] + 4 * channel.indices[ 1 ];
+				var keyframe;
+				var empty = true;
+				var i, l; // check, if values of a property are missing in our keyframes
 
-							data[ time ][ index ] = value;
+				for ( i = 0, l = keyframes.length; i < l; i ++ ) {
 
-						} else {
+					keyframe = keyframes[ i ];
 
-							for ( j = 0, jl = outputSource.stride; j < jl; j ++ ) {
+					if ( keyframe.value[ property ] === undefined ) {
 
-								data[ time ][ j ] = outputSource.array[ stride + j ];
+						keyframe.value[ property ] = null; // mark as missing
 
-							}
+					} else {
 
-						}
+						empty = false;
 
 					}
 
-					break;
-
-				case 'translate':
-					console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform );
-					break;
+				}
 
-				case 'rotate':
-					console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform );
-					break;
+				if ( empty === true ) {
 
-				case 'scale':
-					console.warn( 'THREE.ColladaLoader: Animation transform type "%s" not yet implemented.', transform );
-					break;
+					// no values at all, so we set a default value
+					for ( i = 0, l = keyframes.length; i < l; i ++ ) {
 
-			}
+						keyframe = keyframes[ i ];
+						keyframe.value[ property ] = defaultValue;
 
-			var keyframes = prepareAnimationData( data, defaultMatrix );
+					}
 
-			var animation = {
-				name: object3D.uuid,
-				keyframes: keyframes
-			};
+				} else {
 
-			return animation;
+					// filling gaps
+					createMissingKeyframes( keyframes, property );
 
-		}
+				}
 
-		function prepareAnimationData( data, defaultMatrix ) {
+			}
 
-			var keyframes = [];
+			function createMissingKeyframes( keyframes, property ) {
 
-			// transfer data into a sortable array
+				var prev, next;
 
-			for ( var time in data ) {
+				for ( var i = 0, l = keyframes.length; i < l; i ++ ) {
 
-				keyframes.push( { time: parseFloat( time ), value: data[ time ] } );
+					var keyframe = keyframes[ i ];
 
-			}
+					if ( keyframe.value[ property ] === null ) {
 
-			// ensure keyframes are sorted by time
+						prev = getPrev( keyframes, i, property );
+						next = getNext( keyframes, i, property );
 
-			keyframes.sort( ascending );
+						if ( prev === null ) {
 
-			// now we clean up all animation data, so we can use them for keyframe tracks
+							keyframe.value[ property ] = next.value[ property ];
+							continue;
 
-			for ( var i = 0; i < 16; i ++ ) {
+						}
 
-				transformAnimationData( keyframes, i, defaultMatrix.elements[ i ] );
+						if ( next === null ) {
 
-			}
+							keyframe.value[ property ] = prev.value[ property ];
+							continue;
 
-			return keyframes;
+						}
 
-			// array sort function
+						interpolate( keyframe, prev, next, property );
 
-			function ascending( a, b ) {
+					}
 
-				return a.time - b.time;
+				}
 
 			}
 
-		}
+			function getPrev( keyframes, i, property ) {
+
+				while ( i >= 0 ) {
 
-		var position = new THREE.Vector3();
-		var scale = new THREE.Vector3();
-		var quaternion = new THREE.Quaternion();
+					var keyframe = keyframes[ i ];
+					if ( keyframe.value[ property ] !== null ) return keyframe;
+					i --;
 
-		function createKeyframeTracks( animation, tracks ) {
+				}
 
-			var keyframes = animation.keyframes;
-			var name = animation.name;
+				return null;
 
-			var times = [];
-			var positionData = [];
-			var quaternionData = [];
-			var scaleData = [];
+			}
 
-			for ( var i = 0, l = keyframes.length; i < l; i ++ ) {
+			function getNext( keyframes, i, property ) {
 
-				var keyframe = keyframes[ i ];
+				while ( i < keyframes.length ) {
 
-				var time = keyframe.time;
-				var value = keyframe.value;
+					var keyframe = keyframes[ i ];
+					if ( keyframe.value[ property ] !== null ) return keyframe;
+					i ++;
 
-				matrix.fromArray( value ).transpose();
-				matrix.decompose( position, quaternion, scale );
+				}
 
-				times.push( time );
-				positionData.push( position.x, position.y, position.z );
-				quaternionData.push( quaternion.x, quaternion.y, quaternion.z, quaternion.w );
-				scaleData.push( scale.x, scale.y, scale.z );
+				return null;
 
 			}
 
-			if ( positionData.length > 0 ) tracks.push( new THREE.VectorKeyframeTrack( name + '.position', times, positionData ) );
-			if ( quaternionData.length > 0 ) tracks.push( new THREE.QuaternionKeyframeTrack( name + '.quaternion', times, quaternionData ) );
-			if ( scaleData.length > 0 ) tracks.push( new THREE.VectorKeyframeTrack( name + '.scale', times, scaleData ) );
+			function interpolate( key, prev, next, property ) {
 
-			return tracks;
+				if ( next.time - prev.time === 0 ) {
 
-		}
+					key.value[ property ] = prev.value[ property ];
+					return;
+
+				}
 
-		function transformAnimationData( keyframes, property, defaultValue ) {
+				key.value[ property ] = ( key.time - prev.time ) * ( next.value[ property ] - prev.value[ property ] ) / ( next.time - prev.time ) + prev.value[ property ];
 
-			var keyframe;
+			} // animation clips
 
-			var empty = true;
-			var i, l;
 
-			// check, if values of a property are missing in our keyframes
+			function parseAnimationClip( xml ) {
 
-			for ( i = 0, l = keyframes.length; i < l; i ++ ) {
+				var data = {
+					name: xml.getAttribute( 'id' ) || 'default',
+					start: parseFloat( xml.getAttribute( 'start' ) || 0 ),
+					end: parseFloat( xml.getAttribute( 'end' ) || 0 ),
+					animations: []
+				};
 
-				keyframe = keyframes[ i ];
+				for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
-				if ( keyframe.value[ property ] === undefined ) {
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-					keyframe.value[ property ] = null; // mark as missing
+					switch ( child.nodeName ) {
 
-				} else {
+						case 'instance_animation':
+							data.animations.push( parseId( child.getAttribute( 'url' ) ) );
+							break;
 
-					empty = false;
+					}
 
 				}
 
+				library.clips[ xml.getAttribute( 'id' ) ] = data;
+
 			}
 
-			if ( empty === true ) {
+			function buildAnimationClip( data ) {
 
-				// no values at all, so we set a default value
+				var tracks = [];
+				var name = data.name;
+				var duration = data.end - data.start || - 1;
+				var animations = data.animations;
 
-				for ( i = 0, l = keyframes.length; i < l; i ++ ) {
+				for ( var i = 0, il = animations.length; i < il; i ++ ) {
 
-					keyframe = keyframes[ i ];
+					var animationTracks = getAnimation( animations[ i ] );
 
-					keyframe.value[ property ] = defaultValue;
+					for ( var j = 0, jl = animationTracks.length; j < jl; j ++ ) {
 
-				}
+						tracks.push( animationTracks[ j ] );
 
-			} else {
+					}
 
-				// filling gaps
+				}
 
-				createMissingKeyframes( keyframes, property );
+				return new THREE.AnimationClip( name, duration, tracks );
 
 			}
 
-		}
-
-		function createMissingKeyframes( keyframes, property ) {
+			function getAnimationClip( id ) {
 
-			var prev, next;
+				return getBuild( library.clips[ id ], buildAnimationClip );
 
-			for ( var i = 0, l = keyframes.length; i < l; i ++ ) {
+			} // controller
 
-				var keyframe = keyframes[ i ];
 
-				if ( keyframe.value[ property ] === null ) {
+			function parseController( xml ) {
 
-					prev = getPrev( keyframes, i, property );
-					next = getNext( keyframes, i, property );
+				var data = {};
 
-					if ( prev === null ) {
+				for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
-						keyframe.value[ property ] = next.value[ property ];
-						continue;
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-					}
+					switch ( child.nodeName ) {
 
-					if ( next === null ) {
+						case 'skin':
+						// there is exactly one skin per controller
+							data.id = parseId( child.getAttribute( 'source' ) );
+							data.skin = parseSkin( child );
+							break;
 
-						keyframe.value[ property ] = prev.value[ property ];
-						continue;
+						case 'morph':
+							data.id = parseId( child.getAttribute( 'source' ) );
+							console.warn( 'THREE.ColladaLoader: Morph target animation not supported yet.' );
+							break;
 
 					}
 
-					interpolate( keyframe, prev, next, property );
-
 				}
 
-			}
-
-		}
+				library.controllers[ xml.getAttribute( 'id' ) ] = data;
 
-		function getPrev( keyframes, i, property ) {
+			}
 
-			while ( i >= 0 ) {
+			function parseSkin( xml ) {
 
-				var keyframe = keyframes[ i ];
+				var data = {
+					sources: {}
+				};
 
-				if ( keyframe.value[ property ] !== null ) return keyframe;
+				for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
-				i --;
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-			}
+					switch ( child.nodeName ) {
 
-			return null;
+						case 'bind_shape_matrix':
+							data.bindShapeMatrix = parseFloats( child.textContent );
+							break;
 
-		}
+						case 'source':
+							var id = child.getAttribute( 'id' );
+							data.sources[ id ] = parseSource( child );
+							break;
 
-		function getNext( keyframes, i, property ) {
+						case 'joints':
+							data.joints = parseJoints( child );
+							break;
 
-			while ( i < keyframes.length ) {
+						case 'vertex_weights':
+							data.vertexWeights = parseVertexWeights( child );
+							break;
 
-				var keyframe = keyframes[ i ];
+					}
 
-				if ( keyframe.value[ property ] !== null ) return keyframe;
+				}
 
-				i ++;
+				return data;
 
 			}
 
-			return null;
+			function parseJoints( xml ) {
 
-		}
+				var data = {
+					inputs: {}
+				};
 
-		function interpolate( key, prev, next, property ) {
+				for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
-			if ( ( next.time - prev.time ) === 0 ) {
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-				key.value[ property ] = prev.value[ property ];
-				return;
+					switch ( child.nodeName ) {
 
-			}
+						case 'input':
+							var semantic = child.getAttribute( 'semantic' );
+							var id = parseId( child.getAttribute( 'source' ) );
+							data.inputs[ semantic ] = id;
+							break;
 
-			key.value[ property ] = ( ( key.time - prev.time ) * ( next.value[ property ] - prev.value[ property ] ) / ( next.time - prev.time ) ) + prev.value[ property ];
+					}
 
-		}
+				}
 
-		// animation clips
+				return data;
 
-		function parseAnimationClip( xml ) {
+			}
 
-			var data = {
-				name: xml.getAttribute( 'id' ) || 'default',
-				start: parseFloat( xml.getAttribute( 'start' ) || 0 ),
-				end: parseFloat( xml.getAttribute( 'end' ) || 0 ),
-				animations: []
-			};
+			function parseVertexWeights( xml ) {
 
-			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+				var data = {
+					inputs: {}
+				};
 
-				var child = xml.childNodes[ i ];
+				for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
-				if ( child.nodeType !== 1 ) continue;
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-				switch ( child.nodeName ) {
+					switch ( child.nodeName ) {
 
-					case 'instance_animation':
-						data.animations.push( parseId( child.getAttribute( 'url' ) ) );
-						break;
+						case 'input':
+							var semantic = child.getAttribute( 'semantic' );
+							var id = parseId( child.getAttribute( 'source' ) );
+							var offset = parseInt( child.getAttribute( 'offset' ) );
+							data.inputs[ semantic ] = {
+								id: id,
+								offset: offset
+							};
+							break;
 
-				}
+						case 'vcount':
+							data.vcount = parseInts( child.textContent );
+							break;
 
-			}
+						case 'v':
+							data.v = parseInts( child.textContent );
+							break;
 
-			library.clips[ xml.getAttribute( 'id' ) ] = data;
+					}
 
-		}
+				}
 
-		function buildAnimationClip( data ) {
+				return data;
 
-			var tracks = [];
+			}
 
-			var name = data.name;
-			var duration = ( data.end - data.start ) || - 1;
-			var animations = data.animations;
+			function buildController( data ) {
 
-			for ( var i = 0, il = animations.length; i < il; i ++ ) {
+				var build = {
+					id: data.id
+				};
+				var geometry = library.geometries[ build.id ];
 
-				var animationTracks = getAnimation( animations[ i ] );
+				if ( data.skin !== undefined ) {
 
-				for ( var j = 0, jl = animationTracks.length; j < jl; j ++ ) {
+					build.skin = buildSkin( data.skin ); // we enhance the 'sources' property of the corresponding geometry with our skin data
 
-					tracks.push( animationTracks[ j ] );
+					geometry.sources.skinIndices = build.skin.indices;
+					geometry.sources.skinWeights = build.skin.weights;
 
 				}
 
-			}
+				return build;
 
-			return new THREE.AnimationClip( name, duration, tracks );
+			}
 
-		}
+			function buildSkin( data ) {
 
-		function getAnimationClip( id ) {
+				var BONE_LIMIT = 4;
+				var build = {
+					joints: [],
+					// this must be an array to preserve the joint order
+					indices: {
+						array: [],
+						stride: BONE_LIMIT
+					},
+					weights: {
+						array: [],
+						stride: BONE_LIMIT
+					}
+				};
+				var sources = data.sources;
+				var vertexWeights = data.vertexWeights;
+				var vcount = vertexWeights.vcount;
+				var v = vertexWeights.v;
+				var jointOffset = vertexWeights.inputs.JOINT.offset;
+				var weightOffset = vertexWeights.inputs.WEIGHT.offset;
+				var jointSource = data.sources[ data.joints.inputs.JOINT ];
+				var inverseSource = data.sources[ data.joints.inputs.INV_BIND_MATRIX ];
+				var weights = sources[ vertexWeights.inputs.WEIGHT.id ].array;
+				var stride = 0;
+				var i, j, l; // procces skin data for each vertex
+
+				for ( i = 0, l = vcount.length; i < l; i ++ ) {
+
+					var jointCount = vcount[ i ]; // this is the amount of joints that affect a single vertex
+
+					var vertexSkinData = [];
+
+					for ( j = 0; j < jointCount; j ++ ) {
+
+						var skinIndex = v[ stride + jointOffset ];
+						var weightId = v[ stride + weightOffset ];
+						var skinWeight = weights[ weightId ];
+						vertexSkinData.push( {
+							index: skinIndex,
+							weight: skinWeight
+						} );
+						stride += 2;
 
-			return getBuild( library.clips[ id ], buildAnimationClip );
+					} // we sort the joints in descending order based on the weights.
+					// this ensures, we only procced the most important joints of the vertex
 
-		}
 
-		// controller
+					vertexSkinData.sort( descending ); // now we provide for each vertex a set of four index and weight values.
+					// the order of the skin data matches the order of vertices
 
-		function parseController( xml ) {
+					for ( j = 0; j < BONE_LIMIT; j ++ ) {
 
-			var data = {};
+						var d = vertexSkinData[ j ];
 
-			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+						if ( d !== undefined ) {
 
-				var child = xml.childNodes[ i ];
+							build.indices.array.push( d.index );
+							build.weights.array.push( d.weight );
 
-				if ( child.nodeType !== 1 ) continue;
+						} else {
 
-				switch ( child.nodeName ) {
+							build.indices.array.push( 0 );
+							build.weights.array.push( 0 );
 
-					case 'skin':
-						// there is exactly one skin per controller
-						data.id = parseId( child.getAttribute( 'source' ) );
-						data.skin = parseSkin( child );
-						break;
+						}
 
-					case 'morph':
-						data.id = parseId( child.getAttribute( 'source' ) );
-						console.warn( 'THREE.ColladaLoader: Morph target animation not supported yet.' );
-						break;
+					}
 
-				}
+				} // setup bind matrix
 
-			}
 
-			library.controllers[ xml.getAttribute( 'id' ) ] = data;
+				if ( data.bindShapeMatrix ) {
 
-		}
+					build.bindMatrix = new THREE.Matrix4().fromArray( data.bindShapeMatrix ).transpose();
 
-		function parseSkin( xml ) {
+				} else {
 
-			var data = {
-				sources: {}
-			};
+					build.bindMatrix = new THREE.Matrix4().identity();
 
-			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+				} // process bones and inverse bind matrix data
 
-				var child = xml.childNodes[ i ];
 
-				if ( child.nodeType !== 1 ) continue;
+				for ( i = 0, l = jointSource.array.length; i < l; i ++ ) {
 
-				switch ( child.nodeName ) {
+					var name = jointSource.array[ i ];
+					var boneInverse = new THREE.Matrix4().fromArray( inverseSource.array, i * inverseSource.stride ).transpose();
+					build.joints.push( {
+						name: name,
+						boneInverse: boneInverse
+					} );
 
-					case 'bind_shape_matrix':
-						data.bindShapeMatrix = parseFloats( child.textContent );
-						break;
+				}
 
-					case 'source':
-						var id = child.getAttribute( 'id' );
-						data.sources[ id ] = parseSource( child );
-						break;
+				return build; // array sort function
 
-					case 'joints':
-						data.joints = parseJoints( child );
-						break;
+				function descending( a, b ) {
 
-					case 'vertex_weights':
-						data.vertexWeights = parseVertexWeights( child );
-						break;
+					return b.weight - a.weight;
 
 				}
 
 			}
 
-			return data;
-
-		}
+			function getController( id ) {
 
-		function parseJoints( xml ) {
+				return getBuild( library.controllers[ id ], buildController );
 
-			var data = {
-				inputs: {}
-			};
+			} // image
 
-			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
-				var child = xml.childNodes[ i ];
+			function parseImage( xml ) {
 
-				if ( child.nodeType !== 1 ) continue;
+				var data = {
+					init_from: getElementsByTagName( xml, 'init_from' )[ 0 ].textContent
+				};
+				library.images[ xml.getAttribute( 'id' ) ] = data;
 
-				switch ( child.nodeName ) {
+			}
 
-					case 'input':
-						var semantic = child.getAttribute( 'semantic' );
-						var id = parseId( child.getAttribute( 'source' ) );
-						data.inputs[ semantic ] = id;
-						break;
+			function buildImage( data ) {
 
-				}
+				if ( data.build !== undefined ) return data.build;
+				return data.init_from;
 
 			}
 
-			return data;
+			function getImage( id ) {
 
-		}
+				var data = library.images[ id ];
 
-		function parseVertexWeights( xml ) {
+				if ( data !== undefined ) {
 
-			var data = {
-				inputs: {}
-			};
+					return getBuild( data, buildImage );
 
-			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+				}
 
-				var child = xml.childNodes[ i ];
+				console.warn( 'THREE.ColladaLoader: Couldn\'t find image with ID:', id );
+				return null;
 
-				if ( child.nodeType !== 1 ) continue;
+			} // effect
 
-				switch ( child.nodeName ) {
 
-					case 'input':
-						var semantic = child.getAttribute( 'semantic' );
-						var id = parseId( child.getAttribute( 'source' ) );
-						var offset = parseInt( child.getAttribute( 'offset' ) );
-						data.inputs[ semantic ] = { id: id, offset: offset };
-						break;
+			function parseEffect( xml ) {
 
-					case 'vcount':
-						data.vcount = parseInts( child.textContent );
-						break;
+				var data = {};
 
-					case 'v':
-						data.v = parseInts( child.textContent );
-						break;
+				for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
-				}
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-			}
+					switch ( child.nodeName ) {
 
-			return data;
+						case 'profile_COMMON':
+							data.profile = parseEffectProfileCOMMON( child );
+							break;
 
-		}
+					}
 
-		function buildController( data ) {
+				}
 
-			var build = {
-				id: data.id
-			};
+				library.effects[ xml.getAttribute( 'id' ) ] = data;
 
-			var geometry = library.geometries[ build.id ];
+			}
 
-			if ( data.skin !== undefined ) {
+			function parseEffectProfileCOMMON( xml ) {
 
-				build.skin = buildSkin( data.skin );
+				var data = {
+					surfaces: {},
+					samplers: {}
+				};
 
-				// we enhance the 'sources' property of the corresponding geometry with our skin data
+				for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
-				geometry.sources.skinIndices = build.skin.indices;
-				geometry.sources.skinWeights = build.skin.weights;
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-			}
+					switch ( child.nodeName ) {
 
-			return build;
+						case 'newparam':
+							parseEffectNewparam( child, data );
+							break;
 
-		}
+						case 'technique':
+							data.technique = parseEffectTechnique( child );
+							break;
 
-		function buildSkin( data ) {
+						case 'extra':
+							data.extra = parseEffectExtra( child );
+							break;
 
-			var BONE_LIMIT = 4;
+					}
 
-			var build = {
-				joints: [], // this must be an array to preserve the joint order
-				indices: {
-					array: [],
-					stride: BONE_LIMIT
-				},
-				weights: {
-					array: [],
-					stride: BONE_LIMIT
 				}
-			};
-
-			var sources = data.sources;
-			var vertexWeights = data.vertexWeights;
-
-			var vcount = vertexWeights.vcount;
-			var v = vertexWeights.v;
-			var jointOffset = vertexWeights.inputs.JOINT.offset;
-			var weightOffset = vertexWeights.inputs.WEIGHT.offset;
 
-			var jointSource = data.sources[ data.joints.inputs.JOINT ];
-			var inverseSource = data.sources[ data.joints.inputs.INV_BIND_MATRIX ];
+				return data;
 
-			var weights = sources[ vertexWeights.inputs.WEIGHT.id ].array;
-			var stride = 0;
+			}
 
-			var i, j, l;
+			function parseEffectNewparam( xml, data ) {
 
-			// procces skin data for each vertex
+				var sid = xml.getAttribute( 'sid' );
 
-			for ( i = 0, l = vcount.length; i < l; i ++ ) {
+				for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
-				var jointCount = vcount[ i ]; // this is the amount of joints that affect a single vertex
-				var vertexSkinData = [];
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-				for ( j = 0; j < jointCount; j ++ ) {
+					switch ( child.nodeName ) {
 
-					var skinIndex = v[ stride + jointOffset ];
-					var weightId = v[ stride + weightOffset ];
-					var skinWeight = weights[ weightId ];
+						case 'surface':
+							data.surfaces[ sid ] = parseEffectSurface( child );
+							break;
 
-					vertexSkinData.push( { index: skinIndex, weight: skinWeight } );
+						case 'sampler2D':
+							data.samplers[ sid ] = parseEffectSampler( child );
+							break;
 
-					stride += 2;
+					}
 
 				}
 
-				// we sort the joints in descending order based on the weights.
-				// this ensures, we only procced the most important joints of the vertex
-
-				vertexSkinData.sort( descending );
-
-				// now we provide for each vertex a set of four index and weight values.
-				// the order of the skin data matches the order of vertices
+			}
 
-				for ( j = 0; j < BONE_LIMIT; j ++ ) {
+			function parseEffectSurface( xml ) {
 
-					var d = vertexSkinData[ j ];
+				var data = {};
 
-					if ( d !== undefined ) {
+				for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
-						build.indices.array.push( d.index );
-						build.weights.array.push( d.weight );
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-					} else {
+					switch ( child.nodeName ) {
 
-						build.indices.array.push( 0 );
-						build.weights.array.push( 0 );
+						case 'init_from':
+							data.init_from = child.textContent;
+							break;
 
 					}
 
 				}
 
+				return data;
+
 			}
 
-			// setup bind matrix
+			function parseEffectSampler( xml ) {
 
-			if ( data.bindShapeMatrix ) {
+				var data = {};
 
-				build.bindMatrix = new THREE.Matrix4().fromArray( data.bindShapeMatrix ).transpose();
+				for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
-			} else {
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-				build.bindMatrix = new THREE.Matrix4().identity();
+					switch ( child.nodeName ) {
 
-			}
+						case 'source':
+							data.source = child.textContent;
+							break;
 
-			// process bones and inverse bind matrix data
-
-			for ( i = 0, l = jointSource.array.length; i < l; i ++ ) {
+					}
 
-				var name = jointSource.array[ i ];
-				var boneInverse = new THREE.Matrix4().fromArray( inverseSource.array, i * inverseSource.stride ).transpose();
+				}
 
-				build.joints.push( { name: name, boneInverse: boneInverse } );
+				return data;
 
 			}
 
-			return build;
-
-			// array sort function
+			function parseEffectTechnique( xml ) {
 
-			function descending( a, b ) {
+				var data = {};
 
-				return b.weight - a.weight;
+				for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
-			}
-
-		}
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-		function getController( id ) {
+					switch ( child.nodeName ) {
 
-			return getBuild( library.controllers[ id ], buildController );
+						case 'constant':
+						case 'lambert':
+						case 'blinn':
+						case 'phong':
+							data.type = child.nodeName;
+							data.parameters = parseEffectParameters( child );
+							break;
 
-		}
+					}
 
-		// image
+				}
 
-		function parseImage( xml ) {
+				return data;
 
-			var data = {
-				init_from: getElementsByTagName( xml, 'init_from' )[ 0 ].textContent
-			};
+			}
 
-			library.images[ xml.getAttribute( 'id' ) ] = data;
+			function parseEffectParameters( xml ) {
 
-		}
+				var data = {};
 
-		function buildImage( data ) {
+				for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
-			if ( data.build !== undefined ) return data.build;
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-			return data.init_from;
+					switch ( child.nodeName ) {
 
-		}
+						case 'emission':
+						case 'diffuse':
+						case 'specular':
+						case 'bump':
+						case 'ambient':
+						case 'shininess':
+						case 'transparency':
+							data[ child.nodeName ] = parseEffectParameter( child );
+							break;
 
-		function getImage( id ) {
+						case 'transparent':
+							data[ child.nodeName ] = {
+								opaque: child.getAttribute( 'opaque' ),
+								data: parseEffectParameter( child )
+							};
+							break;
 
-			var data = library.images[ id ];
+					}
 
-			if ( data !== undefined ) {
+				}
 
-				return getBuild( data, buildImage );
+				return data;
 
 			}
 
-			console.warn( 'THREE.ColladaLoader: Couldn\'t find image with ID:', id );
-
-			return null;
+			function parseEffectParameter( xml ) {
 
-		}
-
-		// effect
+				var data = {};
 
-		function parseEffect( xml ) {
+				for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
-			var data = {};
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+					switch ( child.nodeName ) {
 
-				var child = xml.childNodes[ i ];
+						case 'color':
+							data[ child.nodeName ] = parseFloats( child.textContent );
+							break;
 
-				if ( child.nodeType !== 1 ) continue;
+						case 'float':
+							data[ child.nodeName ] = parseFloat( child.textContent );
+							break;
 
-				switch ( child.nodeName ) {
+						case 'texture':
+							data[ child.nodeName ] = {
+								id: child.getAttribute( 'texture' ),
+								extra: parseEffectParameterTexture( child )
+							};
+							break;
 
-					case 'profile_COMMON':
-						data.profile = parseEffectProfileCOMMON( child );
-						break;
+					}
 
 				}
 
-			}
-
-			library.effects[ xml.getAttribute( 'id' ) ] = data;
-
-		}
-
-		function parseEffectProfileCOMMON( xml ) {
+				return data;
 
-			var data = {
-				surfaces: {},
-				samplers: {}
-			};
+			}
 
-			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+			function parseEffectParameterTexture( xml ) {
 
-				var child = xml.childNodes[ i ];
+				var data = {
+					technique: {}
+				};
 
-				if ( child.nodeType !== 1 ) continue;
+				for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
-				switch ( child.nodeName ) {
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-					case 'newparam':
-						parseEffectNewparam( child, data );
-						break;
+					switch ( child.nodeName ) {
 
-					case 'technique':
-						data.technique = parseEffectTechnique( child );
-						break;
+						case 'extra':
+							parseEffectParameterTextureExtra( child, data );
+							break;
 
-					case 'extra':
-						data.extra = parseEffectExtra( child );
-						break;
+					}
 
 				}
 
-			}
-
-			return data;
-
-		}
+				return data;
 
-		function parseEffectNewparam( xml, data ) {
-
-			var sid = xml.getAttribute( 'sid' );
+			}
 
-			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+			function parseEffectParameterTextureExtra( xml, data ) {
 
-				var child = xml.childNodes[ i ];
+				for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
-				if ( child.nodeType !== 1 ) continue;
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-				switch ( child.nodeName ) {
+					switch ( child.nodeName ) {
 
-					case 'surface':
-						data.surfaces[ sid ] = parseEffectSurface( child );
-						break;
+						case 'technique':
+							parseEffectParameterTextureExtraTechnique( child, data );
+							break;
 
-					case 'sampler2D':
-						data.samplers[ sid ] = parseEffectSampler( child );
-						break;
+					}
 
 				}
 
 			}
 
-		}
-
-		function parseEffectSurface( xml ) {
-
-			var data = {};
-
-			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
-
-				var child = xml.childNodes[ i ];
-
-				if ( child.nodeType !== 1 ) continue;
+			function parseEffectParameterTextureExtraTechnique( xml, data ) {
 
-				switch ( child.nodeName ) {
+				for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
-					case 'init_from':
-						data.init_from = child.textContent;
-						break;
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-				}
+					switch ( child.nodeName ) {
 
-			}
+						case 'repeatU':
+						case 'repeatV':
+						case 'offsetU':
+						case 'offsetV':
+							data.technique[ child.nodeName ] = parseFloat( child.textContent );
+							break;
 
-			return data;
+						case 'wrapU':
+						case 'wrapV':
+						// some files have values for wrapU/wrapV which become NaN via parseInt
+							if ( child.textContent.toUpperCase() === 'TRUE' ) {
 
-		}
+								data.technique[ child.nodeName ] = 1;
 
-		function parseEffectSampler( xml ) {
+							} else if ( child.textContent.toUpperCase() === 'FALSE' ) {
 
-			var data = {};
+								data.technique[ child.nodeName ] = 0;
 
-			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+							} else {
 
-				var child = xml.childNodes[ i ];
+								data.technique[ child.nodeName ] = parseInt( child.textContent );
 
-				if ( child.nodeType !== 1 ) continue;
+							}
 
-				switch ( child.nodeName ) {
+							break;
 
-					case 'source':
-						data.source = child.textContent;
-						break;
+					}
 
 				}
 
 			}
 
-			return data;
-
-		}
-
-		function parseEffectTechnique( xml ) {
+			function parseEffectExtra( xml ) {
 
-			var data = {};
+				var data = {};
 
-			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+				for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
-				var child = xml.childNodes[ i ];
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-				if ( child.nodeType !== 1 ) continue;
+					switch ( child.nodeName ) {
 
-				switch ( child.nodeName ) {
+						case 'technique':
+							data.technique = parseEffectExtraTechnique( child );
+							break;
 
-					case 'constant':
-					case 'lambert':
-					case 'blinn':
-					case 'phong':
-						data.type = child.nodeName;
-						data.parameters = parseEffectParameters( child );
-						break;
+					}
 
 				}
 
-			}
-
-			return data;
+				return data;
 
-		}
+			}
 
-		function parseEffectParameters( xml ) {
+			function parseEffectExtraTechnique( xml ) {
 
-			var data = {};
+				var data = {};
 
-			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+				for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
-				var child = xml.childNodes[ i ];
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-				if ( child.nodeType !== 1 ) continue;
+					switch ( child.nodeName ) {
 
-				switch ( child.nodeName ) {
+						case 'double_sided':
+							data[ child.nodeName ] = parseInt( child.textContent );
+							break;
 
-					case 'emission':
-					case 'diffuse':
-					case 'specular':
-					case 'bump':
-					case 'ambient':
-					case 'shininess':
-					case 'transparency':
-						data[ child.nodeName ] = parseEffectParameter( child );
-						break;
-					case 'transparent':
-						data[ child.nodeName ] = {
-							opaque: child.getAttribute( 'opaque' ),
-							data: parseEffectParameter( child )
-						};
-						break;
+					}
 
 				}
 
+				return data;
+
 			}
 
-			return data;
+			function buildEffect( data ) {
 
-		}
+				return data;
 
-		function parseEffectParameter( xml ) {
+			}
 
-			var data = {};
+			function getEffect( id ) {
 
-			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+				return getBuild( library.effects[ id ], buildEffect );
 
-				var child = xml.childNodes[ i ];
+			} // material
 
-				if ( child.nodeType !== 1 ) continue;
 
-				switch ( child.nodeName ) {
+			function parseMaterial( xml ) {
 
-					case 'color':
-						data[ child.nodeName ] = parseFloats( child.textContent );
-						break;
+				var data = {
+					name: xml.getAttribute( 'name' )
+				};
 
-					case 'float':
-						data[ child.nodeName ] = parseFloat( child.textContent );
-						break;
+				for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
-					case 'texture':
-						data[ child.nodeName ] = { id: child.getAttribute( 'texture' ), extra: parseEffectParameterTexture( child ) };
-						break;
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-				}
+					switch ( child.nodeName ) {
 
-			}
+						case 'instance_effect':
+							data.url = parseId( child.getAttribute( 'url' ) );
+							break;
 
-			return data;
+					}
 
-		}
+				}
 
-		function parseEffectParameterTexture( xml ) {
+				library.materials[ xml.getAttribute( 'id' ) ] = data;
 
-			var data = {
-				technique: {}
-			};
+			}
 
-			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+			function getTextureLoader( image ) {
 
-				var child = xml.childNodes[ i ];
+				var loader;
+				var extension = image.slice( ( image.lastIndexOf( '.' ) - 1 >>> 0 ) + 2 ); // http://www.jstips.co/en/javascript/get-file-extension/
 
-				if ( child.nodeType !== 1 ) continue;
+				extension = extension.toLowerCase();
 
-				switch ( child.nodeName ) {
+				switch ( extension ) {
 
-					case 'extra':
-						parseEffectParameterTextureExtra( child, data );
+					case 'tga':
+						loader = tgaLoader;
 						break;
 
-				}
+					default:
+						loader = textureLoader;
 
-			}
+				}
 
-			return data;
+				return loader;
 
-		}
+			}
 
-		function parseEffectParameterTextureExtra( xml, data ) {
+			function buildMaterial( data ) {
 
-			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+				var effect = getEffect( data.url );
+				var technique = effect.profile.technique;
+				var extra = effect.profile.extra;
+				var material;
 
-				var child = xml.childNodes[ i ];
+				switch ( technique.type ) {
 
-				if ( child.nodeType !== 1 ) continue;
+					case 'phong':
+					case 'blinn':
+						material = new THREE.MeshPhongMaterial();
+						break;
 
-				switch ( child.nodeName ) {
+					case 'lambert':
+						material = new THREE.MeshLambertMaterial();
+						break;
 
-					case 'technique':
-						parseEffectParameterTextureExtraTechnique( child, data );
+					default:
+						material = new THREE.MeshBasicMaterial();
 						break;
 
 				}
 
-			}
+				material.name = data.name || '';
 
-		}
+				function getTexture( textureObject ) {
 
-		function parseEffectParameterTextureExtraTechnique( xml, data ) {
+					var sampler = effect.profile.samplers[ textureObject.id ];
+					var image = null; // get image
 
-			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+					if ( sampler !== undefined ) {
 
-				var child = xml.childNodes[ i ];
+						var surface = effect.profile.surfaces[ sampler.source ];
+						image = getImage( surface.init_from );
 
-				if ( child.nodeType !== 1 ) continue;
+					} else {
 
-				switch ( child.nodeName ) {
+						console.warn( 'THREE.ColladaLoader: Undefined sampler. Access image directly (see #12530).' );
+						image = getImage( textureObject.id );
 
-					case 'repeatU':
-					case 'repeatV':
-					case 'offsetU':
-					case 'offsetV':
-						data.technique[ child.nodeName ] = parseFloat( child.textContent );
-						break;
+					} // create texture if image is avaiable
 
-					case 'wrapU':
-					case 'wrapV':
 
-						// some files have values for wrapU/wrapV which become NaN via parseInt
+					if ( image !== null ) {
 
-						if ( child.textContent.toUpperCase() === 'TRUE' ) {
+						var loader = getTextureLoader( image );
 
-							data.technique[ child.nodeName ] = 1;
+						if ( loader !== undefined ) {
 
-						} else if ( child.textContent.toUpperCase() === 'FALSE' ) {
+							var texture = loader.load( image );
+							var extra = textureObject.extra;
 
-							data.technique[ child.nodeName ] = 0;
+							if ( extra !== undefined && extra.technique !== undefined && isEmpty( extra.technique ) === false ) {
 
-						} else {
+								var technique = extra.technique;
+								texture.wrapS = technique.wrapU ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping;
+								texture.wrapT = technique.wrapV ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping;
+								texture.offset.set( technique.offsetU || 0, technique.offsetV || 0 );
+								texture.repeat.set( technique.repeatU || 1, technique.repeatV || 1 );
 
-							data.technique[ child.nodeName ] = parseInt( child.textContent );
+							} else {
 
-						}
+								texture.wrapS = THREE.RepeatWrapping;
+								texture.wrapT = THREE.RepeatWrapping;
 
-						break;
+							}
 
-				}
+							return texture;
 
-			}
+						} else {
 
-		}
+							console.warn( 'THREE.ColladaLoader: THREE.Loader for texture %s not found.', image );
+							return null;
 
-		function parseEffectExtra( xml ) {
+						}
 
-			var data = {};
+					} else {
 
-			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+						console.warn( 'THREE.ColladaLoader: Couldn\'t create texture with ID:', textureObject.id );
+						return null;
 
-				var child = xml.childNodes[ i ];
+					}
 
-				if ( child.nodeType !== 1 ) continue;
+				}
 
-				switch ( child.nodeName ) {
+				var parameters = technique.parameters;
 
-					case 'technique':
-						data.technique = parseEffectExtraTechnique( child );
-						break;
+				for ( var key in parameters ) {
 
-				}
+					var parameter = parameters[ key ];
 
-			}
+					switch ( key ) {
 
-			return data;
+						case 'diffuse':
+							if ( parameter.color ) material.color.fromArray( parameter.color );
+							if ( parameter.texture ) material.map = getTexture( parameter.texture );
+							break;
 
-		}
+						case 'specular':
+							if ( parameter.color && material.specular ) material.specular.fromArray( parameter.color );
+							if ( parameter.texture ) material.specularMap = getTexture( parameter.texture );
+							break;
 
-		function parseEffectExtraTechnique( xml ) {
+						case 'bump':
+							if ( parameter.texture ) material.normalMap = getTexture( parameter.texture );
+							break;
 
-			var data = {};
+						case 'ambient':
+							if ( parameter.texture ) material.lightMap = getTexture( parameter.texture );
+							break;
 
-			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+						case 'shininess':
+							if ( parameter.float && material.shininess ) material.shininess = parameter.float;
+							break;
 
-				var child = xml.childNodes[ i ];
+						case 'emission':
+							if ( parameter.color && material.emissive ) material.emissive.fromArray( parameter.color );
+							if ( parameter.texture ) material.emissiveMap = getTexture( parameter.texture );
+							break;
 
-				if ( child.nodeType !== 1 ) continue;
+					}
 
-				switch ( child.nodeName ) {
+				} //
 
-					case 'double_sided':
-						data[ child.nodeName ] = parseInt( child.textContent );
-						break;
 
-				}
+				var transparent = parameters[ 'transparent' ];
+				var transparency = parameters[ 'transparency' ]; // <transparency> does not exist but <transparent>
 
-			}
+				if ( transparency === undefined && transparent ) {
 
-			return data;
+					transparency = {
+						float: 1
+					};
 
-		}
+				} // <transparent> does not exist but <transparency>
 
-		function buildEffect( data ) {
 
-			return data;
+				if ( transparent === undefined && transparency ) {
 
-		}
+					transparent = {
+						opaque: 'A_ONE',
+						data: {
+							color: [ 1, 1, 1, 1 ]
+						}
+					};
 
-		function getEffect( id ) {
+				}
 
-			return getBuild( library.effects[ id ], buildEffect );
+				if ( transparent && transparency ) {
 
-		}
+					// handle case if a texture exists but no color
+					if ( transparent.data.texture ) {
 
-		// material
+						// we do not set an alpha map (see #13792)
+						material.transparent = true;
 
-		function parseMaterial( xml ) {
+					} else {
 
-			var data = {
-				name: xml.getAttribute( 'name' )
-			};
+						var color = transparent.data.color;
 
-			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+						switch ( transparent.opaque ) {
 
-				var child = xml.childNodes[ i ];
+							case 'A_ONE':
+								material.opacity = color[ 3 ] * transparency.float;
+								break;
 
-				if ( child.nodeType !== 1 ) continue;
+							case 'RGB_ZERO':
+								material.opacity = 1 - color[ 0 ] * transparency.float;
+								break;
 
-				switch ( child.nodeName ) {
+							case 'A_ZERO':
+								material.opacity = 1 - color[ 3 ] * transparency.float;
+								break;
 
-					case 'instance_effect':
-						data.url = parseId( child.getAttribute( 'url' ) );
-						break;
+							case 'RGB_ONE':
+								material.opacity = color[ 0 ] * transparency.float;
+								break;
 
-				}
+							default:
+								console.warn( 'THREE.ColladaLoader: Invalid opaque type "%s" of transparent tag.', transparent.opaque );
 
-			}
+						}
 
-			library.materials[ xml.getAttribute( 'id' ) ] = data;
+						if ( material.opacity < 1 ) material.transparent = true;
 
-		}
+					}
 
-		function getTextureLoader( image ) {
+				} //
 
-			var loader;
 
-			var extension = image.slice( ( image.lastIndexOf( '.' ) - 1 >>> 0 ) + 2 ); // http://www.jstips.co/en/javascript/get-file-extension/
-			extension = extension.toLowerCase();
+				if ( extra !== undefined && extra.technique !== undefined && extra.technique.double_sided === 1 ) {
 
-			switch ( extension ) {
+					material.side = THREE.DoubleSide;
 
-				case 'tga':
-					loader = tgaLoader;
-					break;
+				}
 
-				default:
-					loader = textureLoader;
+				return material;
 
 			}
 
-			return loader;
+			function getMaterial( id ) {
 
-		}
+				return getBuild( library.materials[ id ], buildMaterial );
 
-		function buildMaterial( data ) {
+			} // camera
 
-			var effect = getEffect( data.url );
-			var technique = effect.profile.technique;
-			var extra = effect.profile.extra;
 
-			var material;
+			function parseCamera( xml ) {
 
-			switch ( technique.type ) {
+				var data = {
+					name: xml.getAttribute( 'name' )
+				};
 
-				case 'phong':
-				case 'blinn':
-					material = new THREE.MeshPhongMaterial();
-					break;
+				for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
-				case 'lambert':
-					material = new THREE.MeshLambertMaterial();
-					break;
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-				default:
-					material = new THREE.MeshBasicMaterial();
-					break;
+					switch ( child.nodeName ) {
 
-			}
+						case 'optics':
+							data.optics = parseCameraOptics( child );
+							break;
 
-			material.name = data.name || '';
+					}
 
-			function getTexture( textureObject ) {
+				}
 
-				var sampler = effect.profile.samplers[ textureObject.id ];
-				var image = null;
+				library.cameras[ xml.getAttribute( 'id' ) ] = data;
 
-				// get image
+			}
 
-				if ( sampler !== undefined ) {
+			function parseCameraOptics( xml ) {
 
-					var surface = effect.profile.surfaces[ sampler.source ];
-					image = getImage( surface.init_from );
+				for ( var i = 0; i < xml.childNodes.length; i ++ ) {
 
-				} else {
+					var child = xml.childNodes[ i ];
 
-					console.warn( 'THREE.ColladaLoader: Undefined sampler. Access image directly (see #12530).' );
-					image = getImage( textureObject.id );
+					switch ( child.nodeName ) {
 
-				}
+						case 'technique_common':
+							return parseCameraTechnique( child );
 
-				// create texture if image is avaiable
+					}
 
-				if ( image !== null ) {
+				}
 
-					var loader = getTextureLoader( image );
+				return {};
 
-					if ( loader !== undefined ) {
+			}
 
-						var texture = loader.load( image );
+			function parseCameraTechnique( xml ) {
 
-						var extra = textureObject.extra;
+				var data = {};
 
-						if ( extra !== undefined && extra.technique !== undefined && isEmpty( extra.technique ) === false ) {
+				for ( var i = 0; i < xml.childNodes.length; i ++ ) {
 
-							var technique = extra.technique;
+					var child = xml.childNodes[ i ];
 
-							texture.wrapS = technique.wrapU ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping;
-							texture.wrapT = technique.wrapV ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping;
+					switch ( child.nodeName ) {
 
-							texture.offset.set( technique.offsetU || 0, technique.offsetV || 0 );
-							texture.repeat.set( technique.repeatU || 1, technique.repeatV || 1 );
+						case 'perspective':
+						case 'orthographic':
+							data.technique = child.nodeName;
+							data.parameters = parseCameraParameters( child );
+							break;
 
-						} else {
+					}
 
-							texture.wrapS = THREE.RepeatWrapping;
-							texture.wrapT = THREE.RepeatWrapping;
+				}
 
-						}
+				return data;
 
-						return texture;
+			}
 
-					} else {
+			function parseCameraParameters( xml ) {
 
-						console.warn( 'THREE.ColladaLoader: Loader for texture %s not found.', image );
+				var data = {};
 
-						return null;
+				for ( var i = 0; i < xml.childNodes.length; i ++ ) {
 
-					}
+					var child = xml.childNodes[ i ];
 
-				} else {
+					switch ( child.nodeName ) {
 
-					console.warn( 'THREE.ColladaLoader: Couldn\'t create texture with ID:', textureObject.id );
+						case 'xfov':
+						case 'yfov':
+						case 'xmag':
+						case 'ymag':
+						case 'znear':
+						case 'zfar':
+						case 'aspect_ratio':
+							data[ child.nodeName ] = parseFloat( child.textContent );
+							break;
 
-					return null;
+					}
 
 				}
 
-			}
+				return data;
 
-			var parameters = technique.parameters;
+			}
 
-			for ( var key in parameters ) {
+			function buildCamera( data ) {
 
-				var parameter = parameters[ key ];
+				var camera;
 
-				switch ( key ) {
+				switch ( data.optics.technique ) {
 
-					case 'diffuse':
-						if ( parameter.color ) material.color.fromArray( parameter.color );
-						if ( parameter.texture ) material.map = getTexture( parameter.texture );
-						break;
-					case 'specular':
-						if ( parameter.color && material.specular ) material.specular.fromArray( parameter.color );
-						if ( parameter.texture ) material.specularMap = getTexture( parameter.texture );
-						break;
-					case 'bump':
-						if ( parameter.texture ) material.normalMap = getTexture( parameter.texture );
-						break;
-					case 'ambient':
-						if ( parameter.texture ) material.lightMap = getTexture( parameter.texture );
+					case 'perspective':
+						camera = new THREE.PerspectiveCamera( data.optics.parameters.yfov, data.optics.parameters.aspect_ratio, data.optics.parameters.znear, data.optics.parameters.zfar );
 						break;
-					case 'shininess':
-						if ( parameter.float && material.shininess ) material.shininess = parameter.float;
+
+					case 'orthographic':
+						var ymag = data.optics.parameters.ymag;
+						var xmag = data.optics.parameters.xmag;
+						var aspectRatio = data.optics.parameters.aspect_ratio;
+						xmag = xmag === undefined ? ymag * aspectRatio : xmag;
+						ymag = ymag === undefined ? xmag / aspectRatio : ymag;
+						xmag *= 0.5;
+						ymag *= 0.5;
+						camera = new THREE.OrthographicCamera( - xmag, xmag, ymag, - ymag, // left, right, top, bottom
+							data.optics.parameters.znear, data.optics.parameters.zfar );
 						break;
-					case 'emission':
-						if ( parameter.color && material.emissive ) material.emissive.fromArray( parameter.color );
-						if ( parameter.texture ) material.emissiveMap = getTexture( parameter.texture );
+
+					default:
+						camera = new THREE.PerspectiveCamera();
 						break;
 
 				}
 
-			}
-
-			//
-
-			var transparent = parameters[ 'transparent' ];
-			var transparency = parameters[ 'transparency' ];
-
-			// <transparency> does not exist but <transparent>
-
-			if ( transparency === undefined && transparent ) {
-
-				transparency = {
-					float: 1
-				};
+				camera.name = data.name || '';
+				return camera;
 
 			}
 
-			// <transparent> does not exist but <transparency>
+			function getCamera( id ) {
 
-			if ( transparent === undefined && transparency ) {
+				var data = library.cameras[ id ];
 
-				transparent = {
-					opaque: 'A_ONE',
-					data: {
-						color: [ 1, 1, 1, 1 ]
-					} };
+				if ( data !== undefined ) {
 
-			}
+					return getBuild( data, buildCamera );
 
-			if ( transparent && transparency ) {
+				}
 
-				// handle case if a texture exists but no color
+				console.warn( 'THREE.ColladaLoader: Couldn\'t find camera with ID:', id );
+				return null;
 
-				if ( transparent.data.texture ) {
+			} // light
 
-					// we do not set an alpha map (see #13792)
 
-					material.transparent = true;
+			function parseLight( xml ) {
 
-				} else {
+				var data = {};
 
-					var color = transparent.data.color;
+				for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
-					switch ( transparent.opaque ) {
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-						case 'A_ONE':
-							material.opacity = color[ 3 ] * transparency.float;
-							break;
-						case 'RGB_ZERO':
-							material.opacity = 1 - ( color[ 0 ] * transparency.float );
-							break;
-						case 'A_ZERO':
-							material.opacity = 1 - ( color[ 3 ] * transparency.float );
-							break;
-						case 'RGB_ONE':
-							material.opacity = color[ 0 ] * transparency.float;
+					switch ( child.nodeName ) {
+
+						case 'technique_common':
+							data = parseLightTechnique( child );
 							break;
-						default:
-							console.warn( 'THREE.ColladaLoader: Invalid opaque type "%s" of transparent tag.', transparent.opaque );
 
 					}
 
-					if ( material.opacity < 1 ) material.transparent = true;
-
 				}
 
+				library.lights[ xml.getAttribute( 'id' ) ] = data;
+
 			}
 
-			//
+			function parseLightTechnique( xml ) {
 
-			if ( extra !== undefined && extra.technique !== undefined && extra.technique.double_sided === 1 ) {
+				var data = {};
 
-				material.side = THREE.DoubleSide;
+				for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
-			}
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-			return material;
+					switch ( child.nodeName ) {
 
-		}
+						case 'directional':
+						case 'point':
+						case 'spot':
+						case 'ambient':
+							data.technique = child.nodeName;
+							data.parameters = parseLightParameters( child );
 
-		function getMaterial( id ) {
+					}
 
-			return getBuild( library.materials[ id ], buildMaterial );
+				}
 
-		}
+				return data;
 
-		// camera
+			}
 
-		function parseCamera( xml ) {
+			function parseLightParameters( xml ) {
 
-			var data = {
-				name: xml.getAttribute( 'name' )
-			};
+				var data = {};
 
-			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+				for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
 
-				var child = xml.childNodes[ i ];
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-				if ( child.nodeType !== 1 ) continue;
+					switch ( child.nodeName ) {
 
-				switch ( child.nodeName ) {
+						case 'color':
+							var array = parseFloats( child.textContent );
+							data.color = new THREE.Color().fromArray( array );
+							break;
 
-					case 'optics':
-						data.optics = parseCameraOptics( child );
-						break;
+						case 'falloff_angle':
+							data.falloffAngle = parseFloat( child.textContent );
+							break;
+
+						case 'quadratic_attenuation':
+							var f = parseFloat( child.textContent );
+							data.distance = f ? Math.sqrt( 1 / f ) : 0;
+							break;
+
+					}
 
 				}
 
+				return data;
+
 			}
 
-			library.cameras[ xml.getAttribute( 'id' ) ] = data;
+			function buildLight( data ) {
 
-		}
+				var light;
 
-		function parseCameraOptics( xml ) {
+				switch ( data.technique ) {
 
-			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+					case 'directional':
+						light = new THREE.DirectionalLight();
+						break;
 
-				var child = xml.childNodes[ i ];
+					case 'point':
+						light = new THREE.PointLight();
+						break;
 
-				switch ( child.nodeName ) {
+					case 'spot':
+						light = new THREE.SpotLight();
+						break;
 
-					case 'technique_common':
-						return parseCameraTechnique( child );
+					case 'ambient':
+						light = new THREE.AmbientLight();
+						break;
 
 				}
 
+				if ( data.parameters.color ) light.color.copy( data.parameters.color );
+				if ( data.parameters.distance ) light.distance = data.parameters.distance;
+				return light;
+
 			}
 
-			return {};
+			function getLight( id ) {
 
-		}
+				var data = library.lights[ id ];
 
-		function parseCameraTechnique( xml ) {
+				if ( data !== undefined ) {
 
-			var data = {};
+					return getBuild( data, buildLight );
 
-			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+				}
 
-				var child = xml.childNodes[ i ];
+				console.warn( 'THREE.ColladaLoader: Couldn\'t find light with ID:', id );
+				return null;
 
-				switch ( child.nodeName ) {
+			} // geometry
 
-					case 'perspective':
-					case 'orthographic':
 
-						data.technique = child.nodeName;
-						data.parameters = parseCameraParameters( child );
+			function parseGeometry( xml ) {
 
-						break;
+				var data = {
+					name: xml.getAttribute( 'name' ),
+					sources: {},
+					vertices: {},
+					primitives: []
+				};
+				var mesh = getElementsByTagName( xml, 'mesh' )[ 0 ]; // the following tags inside geometry are not supported yet (see https://github.com/mrdoob/three.js/pull/12606): convex_mesh, spline, brep
 
-				}
+				if ( mesh === undefined ) return;
 
-			}
+				for ( var i = 0; i < mesh.childNodes.length; i ++ ) {
 
-			return data;
+					var child = mesh.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
+					var id = child.getAttribute( 'id' );
 
-		}
+					switch ( child.nodeName ) {
 
-		function parseCameraParameters( xml ) {
+						case 'source':
+							data.sources[ id ] = parseSource( child );
+							break;
 
-			var data = {};
+						case 'vertices':
+						// data.sources[ id ] = data.sources[ parseId( getElementsByTagName( child, 'input' )[ 0 ].getAttribute( 'source' ) ) ];
+							data.vertices = parseGeometryVertices( child );
+							break;
 
-			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+						case 'polygons':
+							console.warn( 'THREE.ColladaLoader: Unsupported primitive type: ', child.nodeName );
+							break;
 
-				var child = xml.childNodes[ i ];
+						case 'lines':
+						case 'linestrips':
+						case 'polylist':
+						case 'triangles':
+							data.primitives.push( parseGeometryPrimitive( child ) );
+							break;
 
-				switch ( child.nodeName ) {
+						default:
+							console.log( child );
 
-					case 'xfov':
-					case 'yfov':
-					case 'xmag':
-					case 'ymag':
-					case 'znear':
-					case 'zfar':
-					case 'aspect_ratio':
-						data[ child.nodeName ] = parseFloat( child.textContent );
-						break;
+					}
 
 				}
 
+				library.geometries[ xml.getAttribute( 'id' ) ] = data;
+
 			}
 
-			return data;
+			function parseSource( xml ) {
 
-		}
+				var data = {
+					array: [],
+					stride: 3
+				};
 
-		function buildCamera( data ) {
+				for ( var i = 0; i < xml.childNodes.length; i ++ ) {
 
-			var camera;
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-			switch ( data.optics.technique ) {
+					switch ( child.nodeName ) {
 
-				case 'perspective':
-					camera = new THREE.PerspectiveCamera(
-						data.optics.parameters.yfov,
-						data.optics.parameters.aspect_ratio,
-						data.optics.parameters.znear,
-						data.optics.parameters.zfar
-					);
-					break;
+						case 'float_array':
+							data.array = parseFloats( child.textContent );
+							break;
 
-				case 'orthographic':
-					var ymag = data.optics.parameters.ymag;
-					var xmag = data.optics.parameters.xmag;
-					var aspectRatio = data.optics.parameters.aspect_ratio;
-
-					xmag = ( xmag === undefined ) ? ( ymag * aspectRatio ) : xmag;
-					ymag = ( ymag === undefined ) ? ( xmag / aspectRatio ) : ymag;
-
-					xmag *= 0.5;
-					ymag *= 0.5;
-
-					camera = new THREE.OrthographicCamera(
-						- xmag, xmag, ymag, - ymag, // left, right, top, bottom
-						data.optics.parameters.znear,
-						data.optics.parameters.zfar
-					);
-					break;
-
-				default:
-					camera = new THREE.PerspectiveCamera();
-					break;
-
-			}
-
-			camera.name = data.name || '';
-
-			return camera;
-
-		}
-
-		function getCamera( id ) {
-
-			var data = library.cameras[ id ];
-
-			if ( data !== undefined ) {
-
-				return getBuild( data, buildCamera );
-
-			}
-
-			console.warn( 'THREE.ColladaLoader: Couldn\'t find camera with ID:', id );
-
-			return null;
-
-		}
-
-		// light
-
-		function parseLight( xml ) {
+						case 'Name_array':
+							data.array = parseStrings( child.textContent );
+							break;
 
-			var data = {};
+						case 'technique_common':
+							var accessor = getElementsByTagName( child, 'accessor' )[ 0 ];
 
-			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+							if ( accessor !== undefined ) {
 
-				var child = xml.childNodes[ i ];
+								data.stride = parseInt( accessor.getAttribute( 'stride' ) );
 
-				if ( child.nodeType !== 1 ) continue;
+							}
 
-				switch ( child.nodeName ) {
+							break;
 
-					case 'technique_common':
-						data = parseLightTechnique( child );
-						break;
+					}
 
 				}
 
-			}
-
-			library.lights[ xml.getAttribute( 'id' ) ] = data;
-
-		}
-
-		function parseLightTechnique( xml ) {
-
-			var data = {};
-
-			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
-
-				var child = xml.childNodes[ i ];
-
-				if ( child.nodeType !== 1 ) continue;
-
-				switch ( child.nodeName ) {
-
-					case 'directional':
-					case 'point':
-					case 'spot':
-					case 'ambient':
-
-						data.technique = child.nodeName;
-						data.parameters = parseLightParameters( child );
-
-				}
+				return data;
 
 			}
 
-			return data;
-
-		}
-
-		function parseLightParameters( xml ) {
-
-			var data = {};
+			function parseGeometryVertices( xml ) {
 
-			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+				var data = {};
 
-				var child = xml.childNodes[ i ];
+				for ( var i = 0; i < xml.childNodes.length; i ++ ) {
 
-				if ( child.nodeType !== 1 ) continue;
-
-				switch ( child.nodeName ) {
-
-					case 'color':
-						var array = parseFloats( child.textContent );
-						data.color = new THREE.Color().fromArray( array );
-						break;
-
-					case 'falloff_angle':
-						data.falloffAngle = parseFloat( child.textContent );
-						break;
-
-					case 'quadratic_attenuation':
-						var f = parseFloat( child.textContent );
-						data.distance = f ? Math.sqrt( 1 / f ) : 0;
-						break;
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
+					data[ child.getAttribute( 'semantic' ) ] = parseId( child.getAttribute( 'source' ) );
 
 				}
 
-			}
-
-			return data;
-
-		}
-
-		function buildLight( data ) {
-
-			var light;
-
-			switch ( data.technique ) {
-
-				case 'directional':
-					light = new THREE.DirectionalLight();
-					break;
-
-				case 'point':
-					light = new THREE.PointLight();
-					break;
-
-				case 'spot':
-					light = new THREE.SpotLight();
-					break;
-
-				case 'ambient':
-					light = new THREE.AmbientLight();
-					break;
-
-			}
-
-			if ( data.parameters.color ) light.color.copy( data.parameters.color );
-			if ( data.parameters.distance ) light.distance = data.parameters.distance;
-
-			return light;
-
-		}
-
-		function getLight( id ) {
-
-			var data = library.lights[ id ];
-
-			if ( data !== undefined ) {
-
-				return getBuild( data, buildLight );
-
-			}
-
-			console.warn( 'THREE.ColladaLoader: Couldn\'t find light with ID:', id );
-
-			return null;
-
-		}
-
-		// geometry
-
-		function parseGeometry( xml ) {
-
-			var data = {
-				name: xml.getAttribute( 'name' ),
-				sources: {},
-				vertices: {},
-				primitives: []
-			};
-
-			var mesh = getElementsByTagName( xml, 'mesh' )[ 0 ];
-
-			// the following tags inside geometry are not supported yet (see https://github.com/mrdoob/three.js/pull/12606): convex_mesh, spline, brep
-			if ( mesh === undefined ) return;
-
-			for ( var i = 0; i < mesh.childNodes.length; i ++ ) {
-
-				var child = mesh.childNodes[ i ];
-
-				if ( child.nodeType !== 1 ) continue;
-
-				var id = child.getAttribute( 'id' );
-
-				switch ( child.nodeName ) {
-
-					case 'source':
-						data.sources[ id ] = parseSource( child );
-						break;
-
-					case 'vertices':
-						// data.sources[ id ] = data.sources[ parseId( getElementsByTagName( child, 'input' )[ 0 ].getAttribute( 'source' ) ) ];
-						data.vertices = parseGeometryVertices( child );
-						break;
-
-					case 'polygons':
-						console.warn( 'THREE.ColladaLoader: Unsupported primitive type: ', child.nodeName );
-						break;
-
-					case 'lines':
-					case 'linestrips':
-					case 'polylist':
-					case 'triangles':
-						data.primitives.push( parseGeometryPrimitive( child ) );
-						break;
-
-					default:
-						console.log( child );
-
-				}
+				return data;
 
 			}
 
-			library.geometries[ xml.getAttribute( 'id' ) ] = data;
-
-		}
+			function parseGeometryPrimitive( xml ) {
 
-		function parseSource( xml ) {
-
-			var data = {
-				array: [],
-				stride: 3
-			};
-
-			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
-
-				var child = xml.childNodes[ i ];
-
-				if ( child.nodeType !== 1 ) continue;
-
-				switch ( child.nodeName ) {
-
-					case 'float_array':
-						data.array = parseFloats( child.textContent );
-						break;
-
-					case 'Name_array':
-						data.array = parseStrings( child.textContent );
-						break;
-
-					case 'technique_common':
-						var accessor = getElementsByTagName( child, 'accessor' )[ 0 ];
+				var primitive = {
+					type: xml.nodeName,
+					material: xml.getAttribute( 'material' ),
+					count: parseInt( xml.getAttribute( 'count' ) ),
+					inputs: {},
+					stride: 0,
+					hasUV: false
+				};
 
-						if ( accessor !== undefined ) {
+				for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
+
+					switch ( child.nodeName ) {
+
+						case 'input':
+							var id = parseId( child.getAttribute( 'source' ) );
+							var semantic = child.getAttribute( 'semantic' );
+							var offset = parseInt( child.getAttribute( 'offset' ) );
+							var set = parseInt( child.getAttribute( 'set' ) );
+							var inputname = set > 0 ? semantic + set : semantic;
+							primitive.inputs[ inputname ] = {
+								id: id,
+								offset: offset
+							};
+							primitive.stride = Math.max( primitive.stride, offset + 1 );
+							if ( semantic === 'TEXCOORD' ) primitive.hasUV = true;
+							break;
 
-							data.stride = parseInt( accessor.getAttribute( 'stride' ) );
+						case 'vcount':
+							primitive.vcount = parseInts( child.textContent );
+							break;
 
-						}
+						case 'p':
+							primitive.p = parseInts( child.textContent );
+							break;
 
-						break;
+					}
 
 				}
 
-			}
-
-			return data;
-
-		}
-
-		function parseGeometryVertices( xml ) {
-
-			var data = {};
-
-			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
-
-				var child = xml.childNodes[ i ];
-
-				if ( child.nodeType !== 1 ) continue;
-
-				data[ child.getAttribute( 'semantic' ) ] = parseId( child.getAttribute( 'source' ) );
+				return primitive;
 
 			}
 
-			return data;
-
-		}
-
-		function parseGeometryPrimitive( xml ) {
-
-			var primitive = {
-				type: xml.nodeName,
-				material: xml.getAttribute( 'material' ),
-				count: parseInt( xml.getAttribute( 'count' ) ),
-				inputs: {},
-				stride: 0,
-				hasUV: false
-			};
-
-			for ( var i = 0, l = xml.childNodes.length; i < l; i ++ ) {
+			function groupPrimitives( primitives ) {
 
-				var child = xml.childNodes[ i ];
+				var build = {};
 
-				if ( child.nodeType !== 1 ) continue;
+				for ( var i = 0; i < primitives.length; i ++ ) {
 
-				switch ( child.nodeName ) {
-
-					case 'input':
-						var id = parseId( child.getAttribute( 'source' ) );
-						var semantic = child.getAttribute( 'semantic' );
-						var offset = parseInt( child.getAttribute( 'offset' ) );
-						var set = parseInt( child.getAttribute( 'set' ) );
-						var inputname = ( set > 0 ? semantic + set : semantic );
-						primitive.inputs[ inputname ] = { id: id, offset: offset };
-						primitive.stride = Math.max( primitive.stride, offset + 1 );
-						if ( semantic === 'TEXCOORD' ) primitive.hasUV = true;
-						break;
-
-					case 'vcount':
-						primitive.vcount = parseInts( child.textContent );
-						break;
-
-					case 'p':
-						primitive.p = parseInts( child.textContent );
-						break;
+					var primitive = primitives[ i ];
+					if ( build[ primitive.type ] === undefined ) build[ primitive.type ] = [];
+					build[ primitive.type ].push( primitive );
 
 				}
 
-			}
-
-			return primitive;
-
-		}
-
-		function groupPrimitives( primitives ) {
-
-			var build = {};
-
-			for ( var i = 0; i < primitives.length; i ++ ) {
-
-				var primitive = primitives[ i ];
-
-				if ( build[ primitive.type ] === undefined ) build[ primitive.type ] = [];
-
-				build[ primitive.type ].push( primitive );
+				return build;
 
 			}
 
-			return build;
-
-		}
+			function checkUVCoordinates( primitives ) {
 
-		function checkUVCoordinates( primitives ) {
+				var count = 0;
 
-			var count = 0;
+				for ( var i = 0, l = primitives.length; i < l; i ++ ) {
 
-			for ( var i = 0, l = primitives.length; i < l; i ++ ) {
+					var primitive = primitives[ i ];
 
-				var primitive = primitives[ i ];
+					if ( primitive.hasUV === true ) {
 
-				if ( primitive.hasUV === true ) {
+						count ++;
 
-					count ++;
+					}
 
 				}
 
-			}
+				if ( count > 0 && count < primitives.length ) {
 
-			if ( count > 0 && count < primitives.length ) {
+					primitives.uvsNeedsFix = true;
 
-				primitives.uvsNeedsFix = true;
+				}
 
 			}
 
-		}
-
-		function buildGeometry( data ) {
-
-			var build = {};
-
-			var sources = data.sources;
-			var vertices = data.vertices;
-			var primitives = data.primitives;
-
-			if ( primitives.length === 0 ) return {};
+			function buildGeometry( data ) {
 
-			// our goal is to create one buffer geometry for a single type of primitives
-			// first, we group all primitives by their type
+				var build = {};
+				var sources = data.sources;
+				var vertices = data.vertices;
+				var primitives = data.primitives;
+				if ( primitives.length === 0 ) return {}; // our goal is to create one buffer geometry for a single type of primitives
+				// first, we group all primitives by their type
 
-			var groupedPrimitives = groupPrimitives( primitives );
+				var groupedPrimitives = groupPrimitives( primitives );
 
-			for ( var type in groupedPrimitives ) {
+				for ( var type in groupedPrimitives ) {
 
-				var primitiveType = groupedPrimitives[ type ];
+					var primitiveType = groupedPrimitives[ type ]; // second, ensure consistent uv coordinates for each type of primitives (polylist,triangles or lines)
 
-				// second, ensure consistent uv coordinates for each type of primitives (polylist,triangles or lines)
+					checkUVCoordinates( primitiveType ); // third, create a buffer geometry for each type of primitives
 
-				checkUVCoordinates( primitiveType );
+					build[ type ] = buildGeometryType( primitiveType, sources, vertices );
 
-				// third, create a buffer geometry for each type of primitives
+				}
 
-				build[ type ] = buildGeometryType( primitiveType, sources, vertices );
+				return build;
 
 			}
 
-			return build;
-
-		}
-
-		function buildGeometryType( primitives, sources, vertices ) {
-
-			var build = {};
-
-			var position = { array: [], stride: 0 };
-			var normal = { array: [], stride: 0 };
-			var uv = { array: [], stride: 0 };
-			var uv2 = { array: [], stride: 0 };
-			var color = { array: [], stride: 0 };
-
-			var skinIndex = { array: [], stride: 4 };
-			var skinWeight = { array: [], stride: 4 };
+			function buildGeometryType( primitives, sources, vertices ) {
 
-			var geometry = new THREE.BufferGeometry();
+				var build = {};
+				var position = {
+					array: [],
+					stride: 0
+				};
+				var normal = {
+					array: [],
+					stride: 0
+				};
+				var uv = {
+					array: [],
+					stride: 0
+				};
+				var uv2 = {
+					array: [],
+					stride: 0
+				};
+				var color = {
+					array: [],
+					stride: 0
+				};
+				var skinIndex = {
+					array: [],
+					stride: 4
+				};
+				var skinWeight = {
+					array: [],
+					stride: 4
+				};
+				var geometry = new THREE.BufferGeometry();
+				var materialKeys = [];
+				var start = 0;
 
-			var materialKeys = [];
+				for ( var p = 0; p < primitives.length; p ++ ) {
 
-			var start = 0;
+					var primitive = primitives[ p ];
+					var inputs = primitive.inputs; // groups
 
-			for ( var p = 0; p < primitives.length; p ++ ) {
+					var count = 0;
 
-				var primitive = primitives[ p ];
-				var inputs = primitive.inputs;
+					switch ( primitive.type ) {
 
-				// groups
+						case 'lines':
+						case 'linestrips':
+							count = primitive.count * 2;
+							break;
 
-				var count = 0;
+						case 'triangles':
+							count = primitive.count * 3;
+							break;
 
-				switch ( primitive.type ) {
+						case 'polylist':
+							for ( var g = 0; g < primitive.count; g ++ ) {
 
-					case 'lines':
-					case 'linestrips':
-						count = primitive.count * 2;
-						break;
+								var vc = primitive.vcount[ g ];
 
-					case 'triangles':
-						count = primitive.count * 3;
-						break;
+								switch ( vc ) {
 
-					case 'polylist':
+									case 3:
+										count += 3; // single triangle
 
-						for ( var g = 0; g < primitive.count; g ++ ) {
+										break;
 
-							var vc = primitive.vcount[ g ];
+									case 4:
+										count += 6; // quad, subdivided into two triangles
 
-							switch ( vc ) {
+										break;
 
-								case 3:
-									count += 3; // single triangle
-									break;
+									default:
+										count += ( vc - 2 ) * 3; // polylist with more than four vertices
 
-								case 4:
-									count += 6; // quad, subdivided into two triangles
-									break;
+										break;
 
-								default:
-									count += ( vc - 2 ) * 3; // polylist with more than four vertices
-									break;
+								}
 
 							}
 
-						}
-
-						break;
+							break;
 
-					default:
-						console.warn( 'THREE.ColladaLoader: Unknow primitive type:', primitive.type );
+						default:
+							console.warn( 'THREE.ColladaLoader: Unknow primitive type:', primitive.type );
 
-				}
+					}
 
-				geometry.addGroup( start, count, p );
-				start += count;
+					geometry.addGroup( start, count, p );
+					start += count; // material
 
-				// material
+					if ( primitive.material ) {
 
-				if ( primitive.material ) {
+						materialKeys.push( primitive.material );
 
-					materialKeys.push( primitive.material );
+					} // geometry data
 
-				}
 
-				// geometry data
+					for ( var name in inputs ) {
 
-				for ( var name in inputs ) {
+						var input = inputs[ name ];
 
-					var input = inputs[ name ];
+						switch ( name ) {
 
-					switch ( name )	{
+							case 'VERTEX':
+								for ( var key in vertices ) {
 
-						case 'VERTEX':
-							for ( var key in vertices ) {
+									var id = vertices[ key ];
 
-								var id = vertices[ key ];
+									switch ( key ) {
 
-								switch ( key ) {
+										case 'POSITION':
+											var prevLength = position.array.length;
+											buildGeometryData( primitive, sources[ id ], input.offset, position.array );
+											position.stride = sources[ id ].stride;
 
-									case 'POSITION':
-										var prevLength = position.array.length;
-										buildGeometryData( primitive, sources[ id ], input.offset, position.array );
-										position.stride = sources[ id ].stride;
+											if ( sources.skinWeights && sources.skinIndices ) {
 
-										if ( sources.skinWeights && sources.skinIndices ) {
+												buildGeometryData( primitive, sources.skinIndices, input.offset, skinIndex.array );
+												buildGeometryData( primitive, sources.skinWeights, input.offset, skinWeight.array );
 
-											buildGeometryData( primitive, sources.skinIndices, input.offset, skinIndex.array );
-											buildGeometryData( primitive, sources.skinWeights, input.offset, skinWeight.array );
+											} // see #3803
 
-										}
 
-										// see #3803
+											if ( primitive.hasUV === false && primitives.uvsNeedsFix === true ) {
 
-										if ( primitive.hasUV === false && primitives.uvsNeedsFix === true ) {
+												var count = ( position.array.length - prevLength ) / position.stride;
 
-											var count = ( position.array.length - prevLength ) / position.stride;
+												for ( var i = 0; i < count; i ++ ) {
 
-											for ( var i = 0; i < count; i ++ ) {
+													// fill missing uv coordinates
+													uv.array.push( 0, 0 );
 
-												// fill missing uv coordinates
-
-												uv.array.push( 0, 0 );
+												}
 
 											}
 
-										}
+											break;
 
-										break;
+										case 'NORMAL':
+											buildGeometryData( primitive, sources[ id ], input.offset, normal.array );
+											normal.stride = sources[ id ].stride;
+											break;
 
-									case 'NORMAL':
-										buildGeometryData( primitive, sources[ id ], input.offset, normal.array );
-										normal.stride = sources[ id ].stride;
-										break;
+										case 'COLOR':
+											buildGeometryData( primitive, sources[ id ], input.offset, color.array );
+											color.stride = sources[ id ].stride;
+											break;
 
-									case 'COLOR':
-										buildGeometryData( primitive, sources[ id ], input.offset, color.array );
-										color.stride = sources[ id ].stride;
-										break;
+										case 'TEXCOORD':
+											buildGeometryData( primitive, sources[ id ], input.offset, uv.array );
+											uv.stride = sources[ id ].stride;
+											break;
 
-									case 'TEXCOORD':
-										buildGeometryData( primitive, sources[ id ], input.offset, uv.array );
-										uv.stride = sources[ id ].stride;
-										break;
+										case 'TEXCOORD1':
+											buildGeometryData( primitive, sources[ id ], input.offset, uv2.array );
+											uv.stride = sources[ id ].stride;
+											break;
 
-									case 'TEXCOORD1':
-										buildGeometryData( primitive, sources[ id ], input.offset, uv2.array );
-										uv.stride = sources[ id ].stride;
-										break;
+										default:
+											console.warn( 'THREE.ColladaLoader: Semantic "%s" not handled in geometry build process.', key );
 
-									default:
-										console.warn( 'THREE.ColladaLoader: Semantic "%s" not handled in geometry build process.', key );
+									}
 
 								}
 
-							}
+								break;
 
-							break;
+							case 'NORMAL':
+								buildGeometryData( primitive, sources[ input.id ], input.offset, normal.array );
+								normal.stride = sources[ input.id ].stride;
+								break;
 
-						case 'NORMAL':
-							buildGeometryData( primitive, sources[ input.id ], input.offset, normal.array );
-							normal.stride = sources[ input.id ].stride;
-							break;
+							case 'COLOR':
+								buildGeometryData( primitive, sources[ input.id ], input.offset, color.array );
+								color.stride = sources[ input.id ].stride;
+								break;
 
-						case 'COLOR':
-							buildGeometryData( primitive, sources[ input.id ], input.offset, color.array );
-							color.stride = sources[ input.id ].stride;
-							break;
+							case 'TEXCOORD':
+								buildGeometryData( primitive, sources[ input.id ], input.offset, uv.array );
+								uv.stride = sources[ input.id ].stride;
+								break;
 
-						case 'TEXCOORD':
-							buildGeometryData( primitive, sources[ input.id ], input.offset, uv.array );
-							uv.stride = sources[ input.id ].stride;
-							break;
+							case 'TEXCOORD1':
+								buildGeometryData( primitive, sources[ input.id ], input.offset, uv2.array );
+								uv2.stride = sources[ input.id ].stride;
+								break;
 
-						case 'TEXCOORD1':
-							buildGeometryData( primitive, sources[ input.id ], input.offset, uv2.array );
-							uv2.stride = sources[ input.id ].stride;
-							break;
+						}
 
 					}
 
-				}
-
-			}
-
-			// build geometry
-
-			if ( position.array.length > 0 ) geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( position.array, position.stride ) );
-			if ( normal.array.length > 0 ) geometry.setAttribute( 'normal', new THREE.Float32BufferAttribute( normal.array, normal.stride ) );
-			if ( color.array.length > 0 ) geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( color.array, color.stride ) );
-			if ( uv.array.length > 0 ) geometry.setAttribute( 'uv', new THREE.Float32BufferAttribute( uv.array, uv.stride ) );
-			if ( uv2.array.length > 0 ) geometry.setAttribute( 'uv2', new THREE.Float32BufferAttribute( uv2.array, uv2.stride ) );
+				} // build geometry
 
-			if ( skinIndex.array.length > 0 ) geometry.setAttribute( 'skinIndex', new THREE.Float32BufferAttribute( skinIndex.array, skinIndex.stride ) );
-			if ( skinWeight.array.length > 0 ) geometry.setAttribute( 'skinWeight', new THREE.Float32BufferAttribute( skinWeight.array, skinWeight.stride ) );
 
-			build.data = geometry;
-			build.type = primitives[ 0 ].type;
-			build.materialKeys = materialKeys;
+				if ( position.array.length > 0 ) geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( position.array, position.stride ) );
+				if ( normal.array.length > 0 ) geometry.setAttribute( 'normal', new THREE.Float32BufferAttribute( normal.array, normal.stride ) );
+				if ( color.array.length > 0 ) geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( color.array, color.stride ) );
+				if ( uv.array.length > 0 ) geometry.setAttribute( 'uv', new THREE.Float32BufferAttribute( uv.array, uv.stride ) );
+				if ( uv2.array.length > 0 ) geometry.setAttribute( 'uv2', new THREE.Float32BufferAttribute( uv2.array, uv2.stride ) );
+				if ( skinIndex.array.length > 0 ) geometry.setAttribute( 'skinIndex', new THREE.Float32BufferAttribute( skinIndex.array, skinIndex.stride ) );
+				if ( skinWeight.array.length > 0 ) geometry.setAttribute( 'skinWeight', new THREE.Float32BufferAttribute( skinWeight.array, skinWeight.stride ) );
+				build.data = geometry;
+				build.type = primitives[ 0 ].type;
+				build.materialKeys = materialKeys;
+				return build;
 
-			return build;
+			}
 
-		}
+			function buildGeometryData( primitive, source, offset, array ) {
 
-		function buildGeometryData( primitive, source, offset, array ) {
+				var indices = primitive.p;
+				var stride = primitive.stride;
+				var vcount = primitive.vcount;
 
-			var indices = primitive.p;
-			var stride = primitive.stride;
-			var vcount = primitive.vcount;
+				function pushVector( i ) {
 
-			function pushVector( i ) {
+					var index = indices[ i + offset ] * sourceStride;
+					var length = index + sourceStride;
 
-				var index = indices[ i + offset ] * sourceStride;
-				var length = index + sourceStride;
+					for ( ; index < length; index ++ ) {
 
-				for ( ; index < length; index ++ ) {
+						array.push( sourceArray[ index ] );
 
-					array.push( sourceArray[ index ] );
+					}
 
 				}
 
-			}
-
-			var sourceArray = source.array;
-			var sourceStride = source.stride;
-
-			if ( primitive.vcount !== undefined ) {
-
-				var index = 0;
+				var sourceArray = source.array;
+				var sourceStride = source.stride;
 
-				for ( var i = 0, l = vcount.length; i < l; i ++ ) {
+				if ( primitive.vcount !== undefined ) {
 
-					var count = vcount[ i ];
+					var index = 0;
 
-					if ( count === 4 ) {
+					for ( var i = 0, l = vcount.length; i < l; i ++ ) {
 
-						var a = index + stride * 0;
-						var b = index + stride * 1;
-						var c = index + stride * 2;
-						var d = index + stride * 3;
+						var count = vcount[ i ];
 
-						pushVector( a ); pushVector( b ); pushVector( d );
-						pushVector( b ); pushVector( c ); pushVector( d );
+						if ( count === 4 ) {
 
-					} else if ( count === 3 ) {
-
-						var a = index + stride * 0;
-						var b = index + stride * 1;
-						var c = index + stride * 2;
+							var a = index + stride * 0;
+							var b = index + stride * 1;
+							var c = index + stride * 2;
+							var d = index + stride * 3;
+							pushVector( a );
+							pushVector( b );
+							pushVector( d );
+							pushVector( b );
+							pushVector( c );
+							pushVector( d );
+
+						} else if ( count === 3 ) {
 
-						pushVector( a ); pushVector( b ); pushVector( c );
+							var a = index + stride * 0;
+							var b = index + stride * 1;
+							var c = index + stride * 2;
+							pushVector( a );
+							pushVector( b );
+							pushVector( c );
 
-					} else if ( count > 4 ) {
+						} else if ( count > 4 ) {
 
-						for ( var k = 1, kl = ( count - 2 ); k <= kl; k ++ ) {
+							for ( var k = 1, kl = count - 2; k <= kl; k ++ ) {
 
-							var a = index + stride * 0;
-							var b = index + stride * k;
-							var c = index + stride * ( k + 1 );
+								var a = index + stride * 0;
+								var b = index + stride * k;
+								var c = index + stride * ( k + 1 );
+								pushVector( a );
+								pushVector( b );
+								pushVector( c );
 
-							pushVector( a ); pushVector( b ); pushVector( c );
+							}
 
 						}
 
-					}
+						index += stride * count;
 
-					index += stride * count;
+					}
 
-				}
+				} else {
 
-			} else {
+					for ( var i = 0, l = indices.length; i < l; i += stride ) {
 
-				for ( var i = 0, l = indices.length; i < l; i += stride ) {
+						pushVector( i );
 
-					pushVector( i );
+					}
 
 				}
 
 			}
 
-		}
+			function getGeometry( id ) {
 
-		function getGeometry( id ) {
+				return getBuild( library.geometries[ id ], buildGeometry );
 
-			return getBuild( library.geometries[ id ], buildGeometry );
+			} // kinematics
 
-		}
-
-		// kinematics
 
-		function parseKinematicsModel( xml ) {
+			function parseKinematicsModel( xml ) {
 
-			var data = {
-				name: xml.getAttribute( 'name' ) || '',
-				joints: {},
-				links: []
-			};
+				var data = {
+					name: xml.getAttribute( 'name' ) || '',
+					joints: {},
+					links: []
+				};
 
-			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+				for ( var i = 0; i < xml.childNodes.length; i ++ ) {
 
-				var child = xml.childNodes[ i ];
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-				if ( child.nodeType !== 1 ) continue;
+					switch ( child.nodeName ) {
 
-				switch ( child.nodeName ) {
+						case 'technique_common':
+							parseKinematicsTechniqueCommon( child, data );
+							break;
 
-					case 'technique_common':
-						parseKinematicsTechniqueCommon( child, data );
-						break;
+					}
 
 				}
 
-			}
-
-			library.kinematicsModels[ xml.getAttribute( 'id' ) ] = data;
-
-		}
-
-		function buildKinematicsModel( data ) {
-
-			if ( data.build !== undefined ) return data.build;
-
-			return data;
-
-		}
+				library.kinematicsModels[ xml.getAttribute( 'id' ) ] = data;
 
-		function getKinematicsModel( id ) {
-
-			return getBuild( library.kinematicsModels[ id ], buildKinematicsModel );
-
-		}
-
-		function parseKinematicsTechniqueCommon( xml, data ) {
-
-			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
-
-				var child = xml.childNodes[ i ];
+			}
 
-				if ( child.nodeType !== 1 ) continue;
+			function buildKinematicsModel( data ) {
 
-				switch ( child.nodeName ) {
+				if ( data.build !== undefined ) return data.build;
+				return data;
 
-					case 'joint':
-						data.joints[ child.getAttribute( 'sid' ) ] = parseKinematicsJoint( child );
-						break;
+			}
 
-					case 'link':
-						data.links.push( parseKinematicsLink( child ) );
-						break;
+			function getKinematicsModel( id ) {
 
-				}
+				return getBuild( library.kinematicsModels[ id ], buildKinematicsModel );
 
 			}
 
-		}
-
-		function parseKinematicsJoint( xml ) {
+			function parseKinematicsTechniqueCommon( xml, data ) {
 
-			var data;
+				for ( var i = 0; i < xml.childNodes.length; i ++ ) {
 
-			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-				var child = xml.childNodes[ i ];
+					switch ( child.nodeName ) {
 
-				if ( child.nodeType !== 1 ) continue;
+						case 'joint':
+							data.joints[ child.getAttribute( 'sid' ) ] = parseKinematicsJoint( child );
+							break;
 
-				switch ( child.nodeName ) {
+						case 'link':
+							data.links.push( parseKinematicsLink( child ) );
+							break;
 
-					case 'prismatic':
-					case 'revolute':
-						data = parseKinematicsJointParameter( child );
-						break;
+					}
 
 				}
 
 			}
 
-			return data;
+			function parseKinematicsJoint( xml ) {
 
-		}
+				var data;
 
-		function parseKinematicsJointParameter( xml, data ) {
-
-			var data = {
-				sid: xml.getAttribute( 'sid' ),
-				name: xml.getAttribute( 'name' ) || '',
-				axis: new THREE.Vector3(),
-				limits: {
-					min: 0,
-					max: 0
-				},
-				type: xml.nodeName,
-				static: false,
-				zeroPosition: 0,
-				middlePosition: 0
-			};
+				for ( var i = 0; i < xml.childNodes.length; i ++ ) {
 
-			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-				var child = xml.childNodes[ i ];
+					switch ( child.nodeName ) {
 
-				if ( child.nodeType !== 1 ) continue;
-
-				switch ( child.nodeName ) {
-
-					case 'axis':
-						var array = parseFloats( child.textContent );
-						data.axis.fromArray( array );
-						break;
-					case 'limits':
-						var max = child.getElementsByTagName( 'max' )[ 0 ];
-						var min = child.getElementsByTagName( 'min' )[ 0 ];
+						case 'prismatic':
+						case 'revolute':
+							data = parseKinematicsJointParameter( child );
+							break;
 
-						data.limits.max = parseFloat( max.textContent );
-						data.limits.min = parseFloat( min.textContent );
-						break;
+					}
 
 				}
 
-			}
-
-			// if min is equal to or greater than max, consider the joint static
-
-			if ( data.limits.min >= data.limits.max ) {
-
-				data.static = true;
+				return data;
 
 			}
 
-			// calculate middle position
+			function parseKinematicsJointParameter( xml, data ) {
 
-			data.middlePosition = ( data.limits.min + data.limits.max ) / 2.0;
+				var data = {
+					sid: xml.getAttribute( 'sid' ),
+					name: xml.getAttribute( 'name' ) || '',
+					axis: new THREE.Vector3(),
+					limits: {
+						min: 0,
+						max: 0
+					},
+					type: xml.nodeName,
+					static: false,
+					zeroPosition: 0,
+					middlePosition: 0
+				};
 
-			return data;
+				for ( var i = 0; i < xml.childNodes.length; i ++ ) {
 
-		}
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-		function parseKinematicsLink( xml ) {
+					switch ( child.nodeName ) {
 
-			var data = {
-				sid: xml.getAttribute( 'sid' ),
-				name: xml.getAttribute( 'name' ) || '',
-				attachments: [],
-				transforms: []
-			};
+						case 'axis':
+							var array = parseFloats( child.textContent );
+							data.axis.fromArray( array );
+							break;
 
-			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+						case 'limits':
+							var max = child.getElementsByTagName( 'max' )[ 0 ];
+							var min = child.getElementsByTagName( 'min' )[ 0 ];
+							data.limits.max = parseFloat( max.textContent );
+							data.limits.min = parseFloat( min.textContent );
+							break;
 
-				var child = xml.childNodes[ i ];
+					}
 
-				if ( child.nodeType !== 1 ) continue;
+				} // if min is equal to or greater than max, consider the joint static
 
-				switch ( child.nodeName ) {
 
-					case 'attachment_full':
-						data.attachments.push( parseKinematicsAttachment( child ) );
-						break;
+				if ( data.limits.min >= data.limits.max ) {
 
-					case 'matrix':
-					case 'translate':
-					case 'rotate':
-						data.transforms.push( parseKinematicsTransform( child ) );
-						break;
+					data.static = true;
 
-				}
+				} // calculate middle position
 
-			}
 
-			return data;
+				data.middlePosition = ( data.limits.min + data.limits.max ) / 2.0;
+				return data;
 
-		}
+			}
 
-		function parseKinematicsAttachment( xml ) {
+			function parseKinematicsLink( xml ) {
 
-			var data = {
-				joint: xml.getAttribute( 'joint' ).split( '/' ).pop(),
-				transforms: [],
-				links: []
-			};
+				var data = {
+					sid: xml.getAttribute( 'sid' ),
+					name: xml.getAttribute( 'name' ) || '',
+					attachments: [],
+					transforms: []
+				};
 
-			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+				for ( var i = 0; i < xml.childNodes.length; i ++ ) {
 
-				var child = xml.childNodes[ i ];
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-				if ( child.nodeType !== 1 ) continue;
+					switch ( child.nodeName ) {
 
-				switch ( child.nodeName ) {
+						case 'attachment_full':
+							data.attachments.push( parseKinematicsAttachment( child ) );
+							break;
 
-					case 'link':
-						data.links.push( parseKinematicsLink( child ) );
-						break;
+						case 'matrix':
+						case 'translate':
+						case 'rotate':
+							data.transforms.push( parseKinematicsTransform( child ) );
+							break;
 
-					case 'matrix':
-					case 'translate':
-					case 'rotate':
-						data.transforms.push( parseKinematicsTransform( child ) );
-						break;
+					}
 
 				}
 
-			}
-
-			return data;
-
-		}
-
-		function parseKinematicsTransform( xml ) {
-
-			var data = {
-				type: xml.nodeName
-			};
-
-			var array = parseFloats( xml.textContent );
-
-			switch ( data.type ) {
-
-				case 'matrix':
-					data.obj = new THREE.Matrix4();
-					data.obj.fromArray( array ).transpose();
-					break;
-
-				case 'translate':
-					data.obj = new THREE.Vector3();
-					data.obj.fromArray( array );
-					break;
-
-				case 'rotate':
-					data.obj = new THREE.Vector3();
-					data.obj.fromArray( array );
-					data.angle = THREE.MathUtils.degToRad( array[ 3 ] );
-					break;
+				return data;
 
 			}
 
-			return data;
+			function parseKinematicsAttachment( xml ) {
 
-		}
-
-		// physics
+				var data = {
+					joint: xml.getAttribute( 'joint' ).split( '/' ).pop(),
+					transforms: [],
+					links: []
+				};
 
-		function parsePhysicsModel( xml ) {
+				for ( var i = 0; i < xml.childNodes.length; i ++ ) {
 
-			var data = {
-				name: xml.getAttribute( 'name' ) || '',
-				rigidBodies: {}
-			};
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+					switch ( child.nodeName ) {
 
-				var child = xml.childNodes[ i ];
-
-				if ( child.nodeType !== 1 ) continue;
+						case 'link':
+							data.links.push( parseKinematicsLink( child ) );
+							break;
 
-				switch ( child.nodeName ) {
+						case 'matrix':
+						case 'translate':
+						case 'rotate':
+							data.transforms.push( parseKinematicsTransform( child ) );
+							break;
 
-					case 'rigid_body':
-						data.rigidBodies[ child.getAttribute( 'name' ) ] = {};
-						parsePhysicsRigidBody( child, data.rigidBodies[ child.getAttribute( 'name' ) ] );
-						break;
+					}
 
 				}
 
-			}
-
-			library.physicsModels[ xml.getAttribute( 'id' ) ] = data;
-
-		}
-
-		function parsePhysicsRigidBody( xml, data ) {
-
-			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
-
-				var child = xml.childNodes[ i ];
-
-				if ( child.nodeType !== 1 ) continue;
-
-				switch ( child.nodeName ) {
-
-					case 'technique_common':
-						parsePhysicsTechniqueCommon( child, data );
-						break;
-
-				}
+				return data;
 
 			}
 
-		}
-
-		function parsePhysicsTechniqueCommon( xml, data ) {
+			function parseKinematicsTransform( xml ) {
 
-			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
-
-				var child = xml.childNodes[ i ];
-
-				if ( child.nodeType !== 1 ) continue;
+				var data = {
+					type: xml.nodeName
+				};
+				var array = parseFloats( xml.textContent );
 
-				switch ( child.nodeName ) {
+				switch ( data.type ) {
 
-					case 'inertia':
-						data.inertia = parseFloats( child.textContent );
+					case 'matrix':
+						data.obj = new THREE.Matrix4();
+						data.obj.fromArray( array ).transpose();
 						break;
 
-					case 'mass':
-						data.mass = parseFloats( child.textContent )[ 0 ];
+					case 'translate':
+						data.obj = new THREE.Vector3();
+						data.obj.fromArray( array );
 						break;
 
-				}
-
-			}
-
-		}
-
-		// scene
-
-		function parseKinematicsScene( xml ) {
-
-			var data = {
-				bindJointAxis: []
-			};
-
-			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
-
-				var child = xml.childNodes[ i ];
-
-				if ( child.nodeType !== 1 ) continue;
-
-				switch ( child.nodeName ) {
-
-					case 'bind_joint_axis':
-						data.bindJointAxis.push( parseKinematicsBindJointAxis( child ) );
+					case 'rotate':
+						data.obj = new THREE.Vector3();
+						data.obj.fromArray( array );
+						data.angle = THREE.MathUtils.degToRad( array[ 3 ] );
 						break;
 
 				}
 
-			}
+				return data;
 
-			library.kinematicsScenes[ parseId( xml.getAttribute( 'url' ) ) ] = data;
+			} // physics
 
-		}
 
-		function parseKinematicsBindJointAxis( xml ) {
+			function parsePhysicsModel( xml ) {
 
-			var data = {
-				target: xml.getAttribute( 'target' ).split( '/' ).pop()
-			};
+				var data = {
+					name: xml.getAttribute( 'name' ) || '',
+					rigidBodies: {}
+				};
 
-			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+				for ( var i = 0; i < xml.childNodes.length; i ++ ) {
 
-				var child = xml.childNodes[ i ];
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-				if ( child.nodeType !== 1 ) continue;
+					switch ( child.nodeName ) {
 
-				switch ( child.nodeName ) {
+						case 'rigid_body':
+							data.rigidBodies[ child.getAttribute( 'name' ) ] = {};
+							parsePhysicsRigidBody( child, data.rigidBodies[ child.getAttribute( 'name' ) ] );
+							break;
 
-					case 'axis':
-						var param = child.getElementsByTagName( 'param' )[ 0 ];
-						data.axis = param.textContent;
-						var tmpJointIndex = data.axis.split( 'inst_' ).pop().split( 'axis' )[ 0 ];
-						data.jointIndex = tmpJointIndex.substr( 0, tmpJointIndex.length - 1 );
-						break;
+					}
 
 				}
 
-			}
-
-			return data;
-
-		}
-
-		function buildKinematicsScene( data ) {
-
-			if ( data.build !== undefined ) return data.build;
-
-			return data;
-
-		}
-
-		function getKinematicsScene( id ) {
-
-			return getBuild( library.kinematicsScenes[ id ], buildKinematicsScene );
-
-		}
-
-		function setupKinematics() {
-
-			var kinematicsModelId = Object.keys( library.kinematicsModels )[ 0 ];
-			var kinematicsSceneId = Object.keys( library.kinematicsScenes )[ 0 ];
-			var visualSceneId = Object.keys( library.visualScenes )[ 0 ];
-
-			if ( kinematicsModelId === undefined || kinematicsSceneId === undefined ) return;
-
-			var kinematicsModel = getKinematicsModel( kinematicsModelId );
-			var kinematicsScene = getKinematicsScene( kinematicsSceneId );
-			var visualScene = getVisualScene( visualSceneId );
-
-			var bindJointAxis = kinematicsScene.bindJointAxis;
-			var jointMap = {};
-
-			for ( var i = 0, l = bindJointAxis.length; i < l; i ++ ) {
-
-				var axis = bindJointAxis[ i ];
-
-				// the result of the following query is an element of type 'translate', 'rotate','scale' or 'matrix'
-
-				var targetElement = collada.querySelector( '[sid="' + axis.target + '"]' );
-
-				if ( targetElement ) {
-
-					// get the parent of the transform element
-
-					var parentVisualElement = targetElement.parentElement;
-
-					// connect the joint of the kinematics model with the element in the visual scene
-
-					connect( axis.jointIndex, parentVisualElement );
-
-				}
+				library.physicsModels[ xml.getAttribute( 'id' ) ] = data;
 
 			}
 
-			function connect( jointIndex, visualElement ) {
+			function parsePhysicsRigidBody( xml, data ) {
 
-				var visualElementName = visualElement.getAttribute( 'name' );
-				var joint = kinematicsModel.joints[ jointIndex ];
+				for ( var i = 0; i < xml.childNodes.length; i ++ ) {
 
-				visualScene.traverse( function ( object ) {
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-					if ( object.name === visualElementName ) {
+					switch ( child.nodeName ) {
 
-						jointMap[ jointIndex ] = {
-							object: object,
-							transforms: buildTransformList( visualElement ),
-							joint: joint,
-							position: joint.zeroPosition
-						};
+						case 'technique_common':
+							parsePhysicsTechniqueCommon( child, data );
+							break;
 
 					}
 
-				} );
+				}
 
 			}
 
-			var m0 = new THREE.Matrix4();
-
-			kinematics = {
-
-				joints: kinematicsModel && kinematicsModel.joints,
+			function parsePhysicsTechniqueCommon( xml, data ) {
 
-				getJointValue: function ( jointIndex ) {
+				for ( var i = 0; i < xml.childNodes.length; i ++ ) {
 
-					var jointData = jointMap[ jointIndex ];
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-					if ( jointData ) {
+					switch ( child.nodeName ) {
 
-						return jointData.position;
-
-					} else {
+						case 'inertia':
+							data.inertia = parseFloats( child.textContent );
+							break;
 
-						console.warn( 'THREE.ColladaLoader: Joint ' + jointIndex + ' doesn\'t exist.' );
+						case 'mass':
+							data.mass = parseFloats( child.textContent )[ 0 ];
+							break;
 
 					}
 
-				},
+				}
 
-				setJointValue: function ( jointIndex, value ) {
+			} // scene
 
-					var jointData = jointMap[ jointIndex ];
 
-					if ( jointData ) {
+			function parseKinematicsScene( xml ) {
 
-						var joint = jointData.joint;
+				var data = {
+					bindJointAxis: []
+				};
+
+				for ( var i = 0; i < xml.childNodes.length; i ++ ) {
 
-						if ( value > joint.limits.max || value < joint.limits.min ) {
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-							console.warn( 'THREE.ColladaLoader: Joint ' + jointIndex + ' value ' + value + ' outside of limits (min: ' + joint.limits.min + ', max: ' + joint.limits.max + ').' );
+					switch ( child.nodeName ) {
 
-						} else if ( joint.static ) {
+						case 'bind_joint_axis':
+							data.bindJointAxis.push( parseKinematicsBindJointAxis( child ) );
+							break;
 
-							console.warn( 'THREE.ColladaLoader: Joint ' + jointIndex + ' is static.' );
+					}
 
-						} else {
+				}
 
-							var object = jointData.object;
-							var axis = joint.axis;
-							var transforms = jointData.transforms;
+				library.kinematicsScenes[ parseId( xml.getAttribute( 'url' ) ) ] = data;
 
-							matrix.identity();
+			}
 
-							// each update, we have to apply all transforms in the correct order
+			function parseKinematicsBindJointAxis( xml ) {
 
-							for ( var i = 0; i < transforms.length; i ++ ) {
+				var data = {
+					target: xml.getAttribute( 'target' ).split( '/' ).pop()
+				};
 
-								var transform = transforms[ i ];
+				for ( var i = 0; i < xml.childNodes.length; i ++ ) {
 
-								// if there is a connection of the transform node with a joint, apply the joint value
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-								if ( transform.sid && transform.sid.indexOf( jointIndex ) !== - 1 ) {
+					switch ( child.nodeName ) {
 
-									switch ( joint.type ) {
+						case 'axis':
+							var param = child.getElementsByTagName( 'param' )[ 0 ];
+							data.axis = param.textContent;
+							var tmpJointIndex = data.axis.split( 'inst_' ).pop().split( 'axis' )[ 0 ];
+							data.jointIndex = tmpJointIndex.substr( 0, tmpJointIndex.length - 1 );
+							break;
 
-										case 'revolute':
-											matrix.multiply( m0.makeRotationAxis( axis, THREE.MathUtils.degToRad( value ) ) );
-											break;
+					}
 
-										case 'prismatic':
-											matrix.multiply( m0.makeTranslation( axis.x * value, axis.y * value, axis.z * value ) );
-											break;
+				}
 
-										default:
-											console.warn( 'THREE.ColladaLoader: Unknown joint type: ' + joint.type );
-											break;
+				return data;
 
-									}
+			}
 
-								} else {
+			function buildKinematicsScene( data ) {
 
-									switch ( transform.type ) {
+				if ( data.build !== undefined ) return data.build;
+				return data;
 
-										case 'matrix':
-											matrix.multiply( transform.obj );
-											break;
+			}
 
-										case 'translate':
-											matrix.multiply( m0.makeTranslation( transform.obj.x, transform.obj.y, transform.obj.z ) );
-											break;
+			function getKinematicsScene( id ) {
 
-										case 'scale':
-											matrix.scale( transform.obj );
-											break;
+				return getBuild( library.kinematicsScenes[ id ], buildKinematicsScene );
 
-										case 'rotate':
-											matrix.multiply( m0.makeRotationAxis( transform.obj, transform.angle ) );
-											break;
+			}
 
-									}
+			function setupKinematics() {
 
-								}
+				var kinematicsModelId = Object.keys( library.kinematicsModels )[ 0 ];
+				var kinematicsSceneId = Object.keys( library.kinematicsScenes )[ 0 ];
+				var visualSceneId = Object.keys( library.visualScenes )[ 0 ];
+				if ( kinematicsModelId === undefined || kinematicsSceneId === undefined ) return;
+				var kinematicsModel = getKinematicsModel( kinematicsModelId );
+				var kinematicsScene = getKinematicsScene( kinematicsSceneId );
+				var visualScene = getVisualScene( visualSceneId );
+				var bindJointAxis = kinematicsScene.bindJointAxis;
+				var jointMap = {};
 
-							}
+				for ( var i = 0, l = bindJointAxis.length; i < l; i ++ ) {
 
-							object.matrix.copy( matrix );
-							object.matrix.decompose( object.position, object.quaternion, object.scale );
+					var axis = bindJointAxis[ i ]; // the result of the following query is an element of type 'translate', 'rotate','scale' or 'matrix'
 
-							jointMap[ jointIndex ].position = value;
+					var targetElement = collada.querySelector( '[sid="' + axis.target + '"]' );
 
-						}
+					if ( targetElement ) {
 
-					} else {
+						// get the parent of the transform element
+						var parentVisualElement = targetElement.parentElement; // connect the joint of the kinematics model with the element in the visual scene
 
-						console.log( 'THREE.ColladaLoader: ' + jointIndex + ' does not exist.' );
+						connect( axis.jointIndex, parentVisualElement );
 
 					}
 
 				}
 
-			};
+				function connect( jointIndex, visualElement ) {
 
-		}
+					var visualElementName = visualElement.getAttribute( 'name' );
+					var joint = kinematicsModel.joints[ jointIndex ];
+					visualScene.traverse( function ( object ) {
 
-		function buildTransformList( node ) {
+						if ( object.name === visualElementName ) {
 
-			var transforms = [];
+							jointMap[ jointIndex ] = {
+								object: object,
+								transforms: buildTransformList( visualElement ),
+								joint: joint,
+								position: joint.zeroPosition
+							};
 
-			var xml = collada.querySelector( '[id="' + node.id + '"]' );
+						}
 
-			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+					} );
 
-				var child = xml.childNodes[ i ];
+				}
 
-				if ( child.nodeType !== 1 ) continue;
+				var m0 = new THREE.Matrix4();
+				kinematics = {
+					joints: kinematicsModel && kinematicsModel.joints,
+					getJointValue: function ( jointIndex ) {
 
-				switch ( child.nodeName ) {
+						var jointData = jointMap[ jointIndex ];
 
-					case 'matrix':
-						var array = parseFloats( child.textContent );
-						var matrix = new THREE.Matrix4().fromArray( array ).transpose();
-						transforms.push( {
-							sid: child.getAttribute( 'sid' ),
-							type: child.nodeName,
-							obj: matrix
-						} );
-						break;
+						if ( jointData ) {
 
-					case 'translate':
-					case 'scale':
-						var array = parseFloats( child.textContent );
-						var vector = new THREE.Vector3().fromArray( array );
-						transforms.push( {
-							sid: child.getAttribute( 'sid' ),
-							type: child.nodeName,
-							obj: vector
-						} );
-						break;
+							return jointData.position;
 
-					case 'rotate':
-						var array = parseFloats( child.textContent );
-						var vector = new THREE.Vector3().fromArray( array );
-						var angle = THREE.MathUtils.degToRad( array[ 3 ] );
-						transforms.push( {
-							sid: child.getAttribute( 'sid' ),
-							type: child.nodeName,
-							obj: vector,
-							angle: angle
-						} );
-						break;
+						} else {
 
-				}
+							console.warn( 'THREE.ColladaLoader: Joint ' + jointIndex + ' doesn\'t exist.' );
 
-			}
+						}
 
-			return transforms;
+					},
+					setJointValue: function ( jointIndex, value ) {
 
-		}
+						var jointData = jointMap[ jointIndex ];
 
-		// nodes
+						if ( jointData ) {
 
-		function prepareNodes( xml ) {
+							var joint = jointData.joint;
 
-			var elements = xml.getElementsByTagName( 'node' );
+							if ( value > joint.limits.max || value < joint.limits.min ) {
 
-			// ensure all node elements have id attributes
+								console.warn( 'THREE.ColladaLoader: Joint ' + jointIndex + ' value ' + value + ' outside of limits (min: ' + joint.limits.min + ', max: ' + joint.limits.max + ').' );
 
-			for ( var i = 0; i < elements.length; i ++ ) {
+							} else if ( joint.static ) {
 
-				var element = elements[ i ];
+								console.warn( 'THREE.ColladaLoader: Joint ' + jointIndex + ' is static.' );
 
-				if ( element.hasAttribute( 'id' ) === false ) {
+							} else {
 
-					element.setAttribute( 'id', generateId() );
+								var object = jointData.object;
+								var axis = joint.axis;
+								var transforms = jointData.transforms;
+								matrix.identity(); // each update, we have to apply all transforms in the correct order
 
-				}
+								for ( var i = 0; i < transforms.length; i ++ ) {
 
-			}
+									var transform = transforms[ i ]; // if there is a connection of the transform node with a joint, apply the joint value
 
-		}
+									if ( transform.sid && transform.sid.indexOf( jointIndex ) !== - 1 ) {
 
-		var matrix = new THREE.Matrix4();
-		var vector = new THREE.Vector3();
-
-		function parseNode( xml ) {
-
-			var data = {
-				name: xml.getAttribute( 'name' ) || '',
-				type: xml.getAttribute( 'type' ),
-				id: xml.getAttribute( 'id' ),
-				sid: xml.getAttribute( 'sid' ),
-				matrix: new THREE.Matrix4(),
-				nodes: [],
-				instanceCameras: [],
-				instanceControllers: [],
-				instanceLights: [],
-				instanceGeometries: [],
-				instanceNodes: [],
-				transforms: {}
-			};
+										switch ( joint.type ) {
 
-			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+											case 'revolute':
+												matrix.multiply( m0.makeRotationAxis( axis, THREE.MathUtils.degToRad( value ) ) );
+												break;
 
-				var child = xml.childNodes[ i ];
+											case 'prismatic':
+												matrix.multiply( m0.makeTranslation( axis.x * value, axis.y * value, axis.z * value ) );
+												break;
 
-				if ( child.nodeType !== 1 ) continue;
+											default:
+												console.warn( 'THREE.ColladaLoader: Unknown joint type: ' + joint.type );
+												break;
 
-				switch ( child.nodeName ) {
+										}
 
-					case 'node':
-						data.nodes.push( child.getAttribute( 'id' ) );
-						parseNode( child );
-						break;
+									} else {
 
-					case 'instance_camera':
-						data.instanceCameras.push( parseId( child.getAttribute( 'url' ) ) );
-						break;
+										switch ( transform.type ) {
 
-					case 'instance_controller':
-						data.instanceControllers.push( parseNodeInstance( child ) );
-						break;
+											case 'matrix':
+												matrix.multiply( transform.obj );
+												break;
 
-					case 'instance_light':
-						data.instanceLights.push( parseId( child.getAttribute( 'url' ) ) );
-						break;
+											case 'translate':
+												matrix.multiply( m0.makeTranslation( transform.obj.x, transform.obj.y, transform.obj.z ) );
+												break;
 
-					case 'instance_geometry':
-						data.instanceGeometries.push( parseNodeInstance( child ) );
-						break;
+											case 'scale':
+												matrix.scale( transform.obj );
+												break;
 
-					case 'instance_node':
-						data.instanceNodes.push( parseId( child.getAttribute( 'url' ) ) );
-						break;
+											case 'rotate':
+												matrix.multiply( m0.makeRotationAxis( transform.obj, transform.angle ) );
+												break;
 
-					case 'matrix':
-						var array = parseFloats( child.textContent );
-						data.matrix.multiply( matrix.fromArray( array ).transpose() );
-						data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName;
-						break;
+										}
 
-					case 'translate':
-						var array = parseFloats( child.textContent );
-						vector.fromArray( array );
-						data.matrix.multiply( matrix.makeTranslation( vector.x, vector.y, vector.z ) );
-						data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName;
-						break;
+									}
 
-					case 'rotate':
-						var array = parseFloats( child.textContent );
-						var angle = THREE.MathUtils.degToRad( array[ 3 ] );
-						data.matrix.multiply( matrix.makeRotationAxis( vector.fromArray( array ), angle ) );
-						data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName;
-						break;
+								}
 
-					case 'scale':
-						var array = parseFloats( child.textContent );
-						data.matrix.scale( vector.fromArray( array ) );
-						data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName;
-						break;
+								object.matrix.copy( matrix );
+								object.matrix.decompose( object.position, object.quaternion, object.scale );
+								jointMap[ jointIndex ].position = value;
 
-					case 'extra':
-						break;
+							}
 
-					default:
-						console.log( child );
+						} else {
 
-				}
+							console.log( 'THREE.ColladaLoader: ' + jointIndex + ' does not exist.' );
+
+						}
+
+					}
+				};
 
 			}
 
-			if ( hasNode( data.id ) ) {
+			function buildTransformList( node ) {
 
-				console.warn( 'THREE.ColladaLoader: There is already a node with ID %s. Exclude current node from further processing.', data.id );
+				var transforms = [];
+				var xml = collada.querySelector( '[id="' + node.id + '"]' );
 
-			} else {
+				for ( var i = 0; i < xml.childNodes.length; i ++ ) {
 
-				library.nodes[ data.id ] = data;
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-			}
+					switch ( child.nodeName ) {
 
-			return data;
+						case 'matrix':
+							var array = parseFloats( child.textContent );
+							var matrix = new THREE.Matrix4().fromArray( array ).transpose();
+							transforms.push( {
+								sid: child.getAttribute( 'sid' ),
+								type: child.nodeName,
+								obj: matrix
+							} );
+							break;
 
-		}
+						case 'translate':
+						case 'scale':
+							var array = parseFloats( child.textContent );
+							var vector = new THREE.Vector3().fromArray( array );
+							transforms.push( {
+								sid: child.getAttribute( 'sid' ),
+								type: child.nodeName,
+								obj: vector
+							} );
+							break;
 
-		function parseNodeInstance( xml ) {
+						case 'rotate':
+							var array = parseFloats( child.textContent );
+							var vector = new THREE.Vector3().fromArray( array );
+							var angle = THREE.MathUtils.degToRad( array[ 3 ] );
+							transforms.push( {
+								sid: child.getAttribute( 'sid' ),
+								type: child.nodeName,
+								obj: vector,
+								angle: angle
+							} );
+							break;
 
-			var data = {
-				id: parseId( xml.getAttribute( 'url' ) ),
-				materials: {},
-				skeletons: []
-			};
+					}
 
-			for ( var i = 0; i < xml.childNodes.length; i ++ ) {
+				}
 
-				var child = xml.childNodes[ i ];
+				return transforms;
 
-				switch ( child.nodeName ) {
+			} // nodes
 
-					case 'bind_material':
-						var instances = child.getElementsByTagName( 'instance_material' );
 
-						for ( var j = 0; j < instances.length; j ++ ) {
+			function prepareNodes( xml ) {
 
-							var instance = instances[ j ];
-							var symbol = instance.getAttribute( 'symbol' );
-							var target = instance.getAttribute( 'target' );
+				var elements = xml.getElementsByTagName( 'node' ); // ensure all node elements have id attributes
 
-							data.materials[ symbol ] = parseId( target );
+				for ( var i = 0; i < elements.length; i ++ ) {
 
-						}
+					var element = elements[ i ];
 
-						break;
+					if ( element.hasAttribute( 'id' ) === false ) {
 
-					case 'skeleton':
-						data.skeletons.push( parseId( child.textContent ) );
-						break;
+						element.setAttribute( 'id', generateId() );
 
-					default:
-						break;
+					}
 
 				}
 
 			}
 
-			return data;
+			var matrix = new THREE.Matrix4();
+			var vector = new THREE.Vector3();
 
-		}
-
-		function buildSkeleton( skeletons, joints ) {
+			function parseNode( xml ) {
 
-			var boneData = [];
-			var sortedBoneData = [];
+				var data = {
+					name: xml.getAttribute( 'name' ) || '',
+					type: xml.getAttribute( 'type' ),
+					id: xml.getAttribute( 'id' ),
+					sid: xml.getAttribute( 'sid' ),
+					matrix: new THREE.Matrix4(),
+					nodes: [],
+					instanceCameras: [],
+					instanceControllers: [],
+					instanceLights: [],
+					instanceGeometries: [],
+					instanceNodes: [],
+					transforms: {}
+				};
 
-			var i, j, data;
+				for ( var i = 0; i < xml.childNodes.length; i ++ ) {
 
-			// a skeleton can have multiple root bones. collada expresses this
-			// situtation with multiple "skeleton" tags per controller instance
+					var child = xml.childNodes[ i ];
+					if ( child.nodeType !== 1 ) continue;
 
-			for ( i = 0; i < skeletons.length; i ++ ) {
+					switch ( child.nodeName ) {
 
-				var skeleton = skeletons[ i ];
+						case 'node':
+							data.nodes.push( child.getAttribute( 'id' ) );
+							parseNode( child );
+							break;
 
-				var root;
+						case 'instance_camera':
+							data.instanceCameras.push( parseId( child.getAttribute( 'url' ) ) );
+							break;
 
-				if ( hasNode( skeleton ) ) {
+						case 'instance_controller':
+							data.instanceControllers.push( parseNodeInstance( child ) );
+							break;
 
-					root = getNode( skeleton );
-					buildBoneHierarchy( root, joints, boneData );
+						case 'instance_light':
+							data.instanceLights.push( parseId( child.getAttribute( 'url' ) ) );
+							break;
 
-				} else if ( hasVisualScene( skeleton ) ) {
+						case 'instance_geometry':
+							data.instanceGeometries.push( parseNodeInstance( child ) );
+							break;
 
-					// handle case where the skeleton refers to the visual scene (#13335)
+						case 'instance_node':
+							data.instanceNodes.push( parseId( child.getAttribute( 'url' ) ) );
+							break;
 
-					var visualScene = library.visualScenes[ skeleton ];
-					var children = visualScene.children;
+						case 'matrix':
+							var array = parseFloats( child.textContent );
+							data.matrix.multiply( matrix.fromArray( array ).transpose() );
+							data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName;
+							break;
 
-					for ( var j = 0; j < children.length; j ++ ) {
+						case 'translate':
+							var array = parseFloats( child.textContent );
+							vector.fromArray( array );
+							data.matrix.multiply( matrix.makeTranslation( vector.x, vector.y, vector.z ) );
+							data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName;
+							break;
 
-						var child = children[ j ];
+						case 'rotate':
+							var array = parseFloats( child.textContent );
+							var angle = THREE.MathUtils.degToRad( array[ 3 ] );
+							data.matrix.multiply( matrix.makeRotationAxis( vector.fromArray( array ), angle ) );
+							data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName;
+							break;
 
-						if ( child.type === 'JOINT' ) {
+						case 'scale':
+							var array = parseFloats( child.textContent );
+							data.matrix.scale( vector.fromArray( array ) );
+							data.transforms[ child.getAttribute( 'sid' ) ] = child.nodeName;
+							break;
 
-							var root = getNode( child.id );
-							buildBoneHierarchy( root, joints, boneData );
+						case 'extra':
+							break;
 
-						}
+						default:
+							console.log( child );
 
 					}
 
+				}
+
+				if ( hasNode( data.id ) ) {
+
+					console.warn( 'THREE.ColladaLoader: There is already a node with ID %s. Exclude current node from further processing.', data.id );
+
 				} else {
 
-					console.error( 'THREE.ColladaLoader: Unable to find root bone of skeleton with ID:', skeleton );
+					library.nodes[ data.id ] = data;
 
 				}
 
+				return data;
+
 			}
 
-			// sort bone data (the order is defined in the corresponding controller)
+			function parseNodeInstance( xml ) {
 
-			for ( i = 0; i < joints.length; i ++ ) {
+				var data = {
+					id: parseId( xml.getAttribute( 'url' ) ),
+					materials: {},
+					skeletons: []
+				};
 
-				for ( j = 0; j < boneData.length; j ++ ) {
+				for ( var i = 0; i < xml.childNodes.length; i ++ ) {
 
-					data = boneData[ j ];
+					var child = xml.childNodes[ i ];
 
-					if ( data.bone.name === joints[ i ].name ) {
+					switch ( child.nodeName ) {
 
-						sortedBoneData[ i ] = data;
-						data.processed = true;
-						break;
+						case 'bind_material':
+							var instances = child.getElementsByTagName( 'instance_material' );
+
+							for ( var j = 0; j < instances.length; j ++ ) {
+
+								var instance = instances[ j ];
+								var symbol = instance.getAttribute( 'symbol' );
+								var target = instance.getAttribute( 'target' );
+								data.materials[ symbol ] = parseId( target );
+
+							}
+
+							break;
+
+						case 'skeleton':
+							data.skeletons.push( parseId( child.textContent ) );
+							break;
+
+						default:
+							break;
 
 					}
 
 				}
 
-			}
+				return data;
 
-			// add unprocessed bone data at the end of the list
+			}
 
-			for ( i = 0; i < boneData.length; i ++ ) {
+			function buildSkeleton( skeletons, joints ) {
 
-				data = boneData[ i ];
+				var boneData = [];
+				var sortedBoneData = [];
+				var i, j, data; // a skeleton can have multiple root bones. collada expresses this
+				// situtation with multiple "skeleton" tags per controller instance
 
-				if ( data.processed === false ) {
+				for ( i = 0; i < skeletons.length; i ++ ) {
 
-					sortedBoneData.push( data );
-					data.processed = true;
+					var skeleton = skeletons[ i ];
+					var root;
 
-				}
+					if ( hasNode( skeleton ) ) {
 
-			}
+						root = getNode( skeleton );
+						buildBoneHierarchy( root, joints, boneData );
 
-			// setup arrays for skeleton creation
+					} else if ( hasVisualScene( skeleton ) ) {
 
-			var bones = [];
-			var boneInverses = [];
+						// handle case where the skeleton refers to the visual scene (#13335)
+						var visualScene = library.visualScenes[ skeleton ];
+						var children = visualScene.children;
 
-			for ( i = 0; i < sortedBoneData.length; i ++ ) {
+						for ( var j = 0; j < children.length; j ++ ) {
 
-				data = sortedBoneData[ i ];
+							var child = children[ j ];
 
-				bones.push( data.bone );
-				boneInverses.push( data.boneInverse );
+							if ( child.type === 'JOINT' ) {
 
-			}
+								var root = getNode( child.id );
+								buildBoneHierarchy( root, joints, boneData );
 
-			return new THREE.Skeleton( bones, boneInverses );
+							}
 
-		}
+						}
 
-		function buildBoneHierarchy( root, joints, boneData ) {
+					} else {
 
-			// setup bone data from visual scene
+						console.error( 'THREE.ColladaLoader: Unable to find root bone of skeleton with ID:', skeleton );
 
-			root.traverse( function ( object ) {
+					}
 
-				if ( object.isBone === true ) {
+				} // sort bone data (the order is defined in the corresponding controller)
 
-					var boneInverse;
 
-					// retrieve the boneInverse from the controller data
+				for ( i = 0; i < joints.length; i ++ ) {
 
-					for ( var i = 0; i < joints.length; i ++ ) {
+					for ( j = 0; j < boneData.length; j ++ ) {
 
-						var joint = joints[ i ];
+						data = boneData[ j ];
 
-						if ( joint.name === object.name ) {
+						if ( data.bone.name === joints[ i ].name ) {
 
-							boneInverse = joint.boneInverse;
+							sortedBoneData[ i ] = data;
+							data.processed = true;
 							break;
 
 						}
 
 					}
 
-					if ( boneInverse === undefined ) {
+				} // add unprocessed bone data at the end of the list
 
-						// Unfortunately, there can be joints in the visual scene that are not part of the
-						// corresponding controller. In this case, we have to create a dummy boneInverse matrix
-						// for the respective bone. This bone won't affect any vertices, because there are no skin indices
-						// and weights defined for it. But we still have to add the bone to the sorted bone list in order to
-						// ensure a correct animation of the model.
 
-						boneInverse = new THREE.Matrix4();
+				for ( i = 0; i < boneData.length; i ++ ) {
 
-					}
+					data = boneData[ i ];
 
-					boneData.push( { bone: object, boneInverse: boneInverse, processed: false } );
+					if ( data.processed === false ) {
 
-				}
+						sortedBoneData.push( data );
+						data.processed = true;
 
-			} );
+					}
 
-		}
+				} // setup arrays for skeleton creation
 
-		function buildNode( data ) {
 
-			var objects = [];
+				var bones = [];
+				var boneInverses = [];
 
-			var matrix = data.matrix;
-			var nodes = data.nodes;
-			var type = data.type;
-			var instanceCameras = data.instanceCameras;
-			var instanceControllers = data.instanceControllers;
-			var instanceLights = data.instanceLights;
-			var instanceGeometries = data.instanceGeometries;
-			var instanceNodes = data.instanceNodes;
+				for ( i = 0; i < sortedBoneData.length; i ++ ) {
 
-			// nodes
+					data = sortedBoneData[ i ];
+					bones.push( data.bone );
+					boneInverses.push( data.boneInverse );
 
-			for ( var i = 0, l = nodes.length; i < l; i ++ ) {
+				}
 
-				objects.push( getNode( nodes[ i ] ) );
+				return new THREE.Skeleton( bones, boneInverses );
 
 			}
 
-			// instance cameras
+			function buildBoneHierarchy( root, joints, boneData ) {
 
-			for ( var i = 0, l = instanceCameras.length; i < l; i ++ ) {
+				// setup bone data from visual scene
+				root.traverse( function ( object ) {
 
-				var instanceCamera = getCamera( instanceCameras[ i ] );
+					if ( object.isBone === true ) {
 
-				if ( instanceCamera !== null ) {
+						var boneInverse; // retrieve the boneInverse from the controller data
 
-					objects.push( instanceCamera.clone() );
+						for ( var i = 0; i < joints.length; i ++ ) {
 
-				}
+							var joint = joints[ i ];
 
-			}
+							if ( joint.name === object.name ) {
 
-			// instance controllers
+								boneInverse = joint.boneInverse;
+								break;
+
+							}
 
-			for ( var i = 0, l = instanceControllers.length; i < l; i ++ ) {
+						}
 
-				var instance = instanceControllers[ i ];
-				var controller = getController( instance.id );
-				var geometries = getGeometry( controller.id );
-				var newObjects = buildObjects( geometries, instance.materials );
+						if ( boneInverse === undefined ) {
 
-				var skeletons = instance.skeletons;
-				var joints = controller.skin.joints;
+							// Unfortunately, there can be joints in the visual scene that are not part of the
+							// corresponding controller. In this case, we have to create a dummy boneInverse matrix
+							// for the respective bone. This bone won't affect any vertices, because there are no skin indices
+							// and weights defined for it. But we still have to add the bone to the sorted bone list in order to
+							// ensure a correct animation of the model.
+							boneInverse = new THREE.Matrix4();
 
-				var skeleton = buildSkeleton( skeletons, joints );
+						}
 
-				for ( var j = 0, jl = newObjects.length; j < jl; j ++ ) {
+						boneData.push( {
+							bone: object,
+							boneInverse: boneInverse,
+							processed: false
+						} );
 
-					var object = newObjects[ j ];
+					}
 
-					if ( object.isSkinnedMesh ) {
+				} );
 
-						object.bind( skeleton, controller.skin.bindMatrix );
-						object.normalizeSkinWeights();
+			}
 
-					}
+			function buildNode( data ) {
 
-					objects.push( object );
+				var objects = [];
+				var matrix = data.matrix;
+				var nodes = data.nodes;
+				var type = data.type;
+				var instanceCameras = data.instanceCameras;
+				var instanceControllers = data.instanceControllers;
+				var instanceLights = data.instanceLights;
+				var instanceGeometries = data.instanceGeometries;
+				var instanceNodes = data.instanceNodes; // nodes
 
-				}
+				for ( var i = 0, l = nodes.length; i < l; i ++ ) {
 
-			}
+					objects.push( getNode( nodes[ i ] ) );
 
-			// instance lights
+				} // instance cameras
 
-			for ( var i = 0, l = instanceLights.length; i < l; i ++ ) {
 
-				var instanceLight = getLight( instanceLights[ i ] );
+				for ( var i = 0, l = instanceCameras.length; i < l; i ++ ) {
 
-				if ( instanceLight !== null ) {
+					var instanceCamera = getCamera( instanceCameras[ i ] );
 
-					objects.push( instanceLight.clone() );
+					if ( instanceCamera !== null ) {
 
-				}
+						objects.push( instanceCamera.clone() );
 
-			}
+					}
 
-			// instance geometries
+				} // instance controllers
 
-			for ( var i = 0, l = instanceGeometries.length; i < l; i ++ ) {
 
-				var instance = instanceGeometries[ i ];
+				for ( var i = 0, l = instanceControllers.length; i < l; i ++ ) {
 
-				// a single geometry instance in collada can lead to multiple object3Ds.
-				// this is the case when primitives are combined like triangles and lines
+					var instance = instanceControllers[ i ];
+					var controller = getController( instance.id );
+					var geometries = getGeometry( controller.id );
+					var newObjects = buildObjects( geometries, instance.materials );
+					var skeletons = instance.skeletons;
+					var joints = controller.skin.joints;
+					var skeleton = buildSkeleton( skeletons, joints );
 
-				var geometries = getGeometry( instance.id );
-				var newObjects = buildObjects( geometries, instance.materials );
+					for ( var j = 0, jl = newObjects.length; j < jl; j ++ ) {
 
-				for ( var j = 0, jl = newObjects.length; j < jl; j ++ ) {
+						var object = newObjects[ j ];
 
-					objects.push( newObjects[ j ] );
+						if ( object.isSkinnedMesh ) {
 
-				}
+							object.bind( skeleton, controller.skin.bindMatrix );
+							object.normalizeSkinWeights();
 
-			}
+						}
 
-			// instance nodes
+						objects.push( object );
 
-			for ( var i = 0, l = instanceNodes.length; i < l; i ++ ) {
+					}
 
-				objects.push( getNode( instanceNodes[ i ] ).clone() );
+				} // instance lights
 
-			}
 
-			var object;
+				for ( var i = 0, l = instanceLights.length; i < l; i ++ ) {
 
-			if ( nodes.length === 0 && objects.length === 1 ) {
+					var instanceLight = getLight( instanceLights[ i ] );
 
-				object = objects[ 0 ];
+					if ( instanceLight !== null ) {
 
-			} else {
+						objects.push( instanceLight.clone() );
 
-				object = ( type === 'JOINT' ) ? new THREE.Bone() : new THREE.Group();
+					}
 
-				for ( var i = 0; i < objects.length; i ++ ) {
+				} // instance geometries
 
-					object.add( objects[ i ] );
 
-				}
+				for ( var i = 0, l = instanceGeometries.length; i < l; i ++ ) {
 
-			}
+					var instance = instanceGeometries[ i ]; // a single geometry instance in collada can lead to multiple object3Ds.
+					// this is the case when primitives are combined like triangles and lines
 
-			object.name = ( type === 'JOINT' ) ? data.sid : data.name;
-			object.matrix.copy( matrix );
-			object.matrix.decompose( object.position, object.quaternion, object.scale );
+					var geometries = getGeometry( instance.id );
+					var newObjects = buildObjects( geometries, instance.materials );
 
-			return object;
+					for ( var j = 0, jl = newObjects.length; j < jl; j ++ ) {
 
-		}
+						objects.push( newObjects[ j ] );
+
+					}
 
-		var fallbackMaterial = new THREE.MeshBasicMaterial( { color: 0xff00ff } );
+				} // instance nodes
 
-		function resolveMaterialBinding( keys, instanceMaterials ) {
 
-			var materials = [];
+				for ( var i = 0, l = instanceNodes.length; i < l; i ++ ) {
 
-			for ( var i = 0, l = keys.length; i < l; i ++ ) {
+					objects.push( getNode( instanceNodes[ i ] ).clone() );
 
-				var id = instanceMaterials[ keys[ i ] ];
+				}
+
+				var object;
 
-				if ( id === undefined ) {
+				if ( nodes.length === 0 && objects.length === 1 ) {
 
-					console.warn( 'THREE.ColladaLoader: Material with key %s not found. Apply fallback material.', keys[ i ] );
-					materials.push( fallbackMaterial );
+					object = objects[ 0 ];
 
 				} else {
 
-					materials.push( getMaterial( id ) );
+					object = type === 'JOINT' ? new THREE.Bone() : new THREE.Group();
 
-				}
+					for ( var i = 0; i < objects.length; i ++ ) {
 
-			}
+						object.add( objects[ i ] );
 
-			return materials;
+					}
 
-		}
+				}
 
-		function buildObjects( geometries, instanceMaterials ) {
+				object.name = type === 'JOINT' ? data.sid : data.name;
+				object.matrix.copy( matrix );
+				object.matrix.decompose( object.position, object.quaternion, object.scale );
+				return object;
 
-			var objects = [];
+			}
 
-			for ( var type in geometries ) {
+			var fallbackMaterial = new THREE.MeshBasicMaterial( {
+				color: 0xff00ff
+			} );
 
-				var geometry = geometries[ type ];
+			function resolveMaterialBinding( keys, instanceMaterials ) {
 
-				var materials = resolveMaterialBinding( geometry.materialKeys, instanceMaterials );
+				var materials = [];
 
-				// handle case if no materials are defined
+				for ( var i = 0, l = keys.length; i < l; i ++ ) {
 
-				if ( materials.length === 0 ) {
+					var id = instanceMaterials[ keys[ i ] ];
 
-					if ( type === 'lines' || type === 'linestrips' ) {
+					if ( id === undefined ) {
 
-						materials.push( new THREE.LineBasicMaterial() );
+						console.warn( 'THREE.ColladaLoader: Material with key %s not found. Apply fallback material.', keys[ i ] );
+						materials.push( fallbackMaterial );
 
 					} else {
 
-						materials.push( new THREE.MeshPhongMaterial() );
+						materials.push( getMaterial( id ) );
 
 					}
 
 				}
 
-				// regard skinning
+				return materials;
 
-				var skinning = ( geometry.data.attributes.skinIndex !== undefined );
+			}
 
-				if ( skinning ) {
+			function buildObjects( geometries, instanceMaterials ) {
 
-					for ( var i = 0, l = materials.length; i < l; i ++ ) {
+				var objects = [];
 
-						materials[ i ].skinning = true;
+				for ( var type in geometries ) {
 
-					}
+					var geometry = geometries[ type ];
+					var materials = resolveMaterialBinding( geometry.materialKeys, instanceMaterials ); // handle case if no materials are defined
 
-				}
+					if ( materials.length === 0 ) {
 
-				// choose between a single or multi materials (material array)
+						if ( type === 'lines' || type === 'linestrips' ) {
 
-				var material = ( materials.length === 1 ) ? materials[ 0 ] : materials;
+							materials.push( new THREE.LineBasicMaterial() );
 
-				// now create a specific 3D object
+						} else {
 
-				var object;
+							materials.push( new THREE.MeshPhongMaterial() );
 
-				switch ( type ) {
+						}
 
-					case 'lines':
-						object = new THREE.LineSegments( geometry.data, material );
-						break;
+					} // regard skinning
 
-					case 'linestrips':
-						object = new THREE.Line( geometry.data, material );
-						break;
 
-					case 'triangles':
-					case 'polylist':
-						if ( skinning ) {
+					var skinning = geometry.data.attributes.skinIndex !== undefined;
 
-							object = new THREE.SkinnedMesh( geometry.data, material );
+					if ( skinning ) {
 
-						} else {
+						for ( var i = 0, l = materials.length; i < l; i ++ ) {
 
-							object = new THREE.Mesh( geometry.data, material );
+							materials[ i ].skinning = true;
 
 						}
 
-						break;
+					} // choose between a single or multi materials (material array)
 
-				}
 
-				objects.push( object );
+					var material = materials.length === 1 ? materials[ 0 ] : materials; // now create a specific 3D object
 
-			}
+					var object;
 
-			return objects;
+					switch ( type ) {
 
-		}
+						case 'lines':
+							object = new THREE.LineSegments( geometry.data, material );
+							break;
 
-		function hasNode( id ) {
+						case 'linestrips':
+							object = new THREE.Line( geometry.data, material );
+							break;
 
-			return library.nodes[ id ] !== undefined;
+						case 'triangles':
+						case 'polylist':
+							if ( skinning ) {
 
-		}
+								object = new THREE.SkinnedMesh( geometry.data, material );
 
-		function getNode( id ) {
+							} else {
 
-			return getBuild( library.nodes[ id ], buildNode );
+								object = new THREE.Mesh( geometry.data, material );
 
-		}
+							}
 
-		// visual scenes
+							break;
 
-		function parseVisualScene( xml ) {
+					}
 
-			var data = {
-				name: xml.getAttribute( 'name' ),
-				children: []
-			};
+					objects.push( object );
 
-			prepareNodes( xml );
+				}
+
+				return objects;
 
-			var elements = getElementsByTagName( xml, 'node' );
+			}
 
-			for ( var i = 0; i < elements.length; i ++ ) {
+			function hasNode( id ) {
 
-				data.children.push( parseNode( elements[ i ] ) );
+				return library.nodes[ id ] !== undefined;
 
 			}
 
-			library.visualScenes[ xml.getAttribute( 'id' ) ] = data;
+			function getNode( id ) {
 
-		}
+				return getBuild( library.nodes[ id ], buildNode );
 
-		function buildVisualScene( data ) {
+			} // visual scenes
 
-			var group = new THREE.Group();
-			group.name = data.name;
 
-			var children = data.children;
+			function parseVisualScene( xml ) {
 
-			for ( var i = 0; i < children.length; i ++ ) {
+				var data = {
+					name: xml.getAttribute( 'name' ),
+					children: []
+				};
+				prepareNodes( xml );
+				var elements = getElementsByTagName( xml, 'node' );
+
+				for ( var i = 0; i < elements.length; i ++ ) {
+
+					data.children.push( parseNode( elements[ i ] ) );
 
-				var child = children[ i ];
+				}
 
-				group.add( getNode( child.id ) );
+				library.visualScenes[ xml.getAttribute( 'id' ) ] = data;
 
 			}
 
-			return group;
+			function buildVisualScene( data ) {
 
-		}
+				var group = new THREE.Group();
+				group.name = data.name;
+				var children = data.children;
 
-		function hasVisualScene( id ) {
+				for ( var i = 0; i < children.length; i ++ ) {
 
-			return library.visualScenes[ id ] !== undefined;
+					var child = children[ i ];
+					group.add( getNode( child.id ) );
 
-		}
+				}
 
-		function getVisualScene( id ) {
+				return group;
 
-			return getBuild( library.visualScenes[ id ], buildVisualScene );
+			}
 
-		}
+			function hasVisualScene( id ) {
 
-		// scenes
+				return library.visualScenes[ id ] !== undefined;
 
-		function parseScene( xml ) {
+			}
 
-			var instance = getElementsByTagName( xml, 'instance_visual_scene' )[ 0 ];
-			return getVisualScene( parseId( instance.getAttribute( 'url' ) ) );
+			function getVisualScene( id ) {
 
-		}
+				return getBuild( library.visualScenes[ id ], buildVisualScene );
 
-		function setupAnimations() {
+			} // scenes
 
-			var clips = library.clips;
 
-			if ( isEmpty( clips ) === true ) {
+			function parseScene( xml ) {
 
-				if ( isEmpty( library.animations ) === false ) {
+				var instance = getElementsByTagName( xml, 'instance_visual_scene' )[ 0 ];
+				return getVisualScene( parseId( instance.getAttribute( 'url' ) ) );
 
-					// if there are animations but no clips, we create a default clip for playback
+			}
 
-					var tracks = [];
+			function setupAnimations() {
 
-					for ( var id in library.animations ) {
+				var clips = library.clips;
 
-						var animationTracks = getAnimation( id );
+				if ( isEmpty( clips ) === true ) {
 
-						for ( var i = 0, l = animationTracks.length; i < l; i ++ ) {
+					if ( isEmpty( library.animations ) === false ) {
 
-							tracks.push( animationTracks[ i ] );
+						// if there are animations but no clips, we create a default clip for playback
+						var tracks = [];
 
-						}
+						for ( var id in library.animations ) {
 
-					}
+							var animationTracks = getAnimation( id );
 
-					animations.push( new THREE.AnimationClip( 'default', - 1, tracks ) );
+							for ( var i = 0, l = animationTracks.length; i < l; i ++ ) {
 
-				}
+								tracks.push( animationTracks[ i ] );
 
-			} else {
+							}
 
-				for ( var id in clips ) {
+						}
 
-					animations.push( getAnimationClip( id ) );
+						animations.push( new THREE.AnimationClip( 'default', - 1, tracks ) );
 
-				}
+					}
 
-			}
+				} else {
 
-		}
+					for ( var id in clips ) {
 
-		// convert the parser error element into text with each child elements text
-		// separated by new lines.
+						animations.push( getAnimationClip( id ) );
 
-		function parserErrorToText( parserError ) {
+					}
 
-			var result = '';
-			var stack = [ parserError ];
+				}
 
-			while ( stack.length ) {
+			} // convert the parser error element into text with each child elements text
+			// separated by new lines.
 
-				var node = stack.shift();
 
-				if ( node.nodeType === Node.TEXT_NODE ) {
+			function parserErrorToText( parserError ) {
 
-					result += node.textContent;
+				var result = '';
+				var stack = [ parserError ];
 
-				} else {
+				while ( stack.length ) {
 
-					result += '\n';
-					stack.push.apply( stack, node.childNodes );
+					var node = stack.shift();
 
-				}
+					if ( node.nodeType === Node.TEXT_NODE ) {
 
-			}
+						result += node.textContent;
 
-			return result.trim();
+					} else {
 
-		}
+						result += '\n';
+						stack.push.apply( stack, node.childNodes );
 
-		if ( text.length === 0 ) {
+					}
 
-			return { scene: new THREE.Scene() };
+				}
 
-		}
+				return result.trim();
 
-		var xml = new DOMParser().parseFromString( text, 'application/xml' );
+			}
 
-		var collada = getElementsByTagName( xml, 'COLLADA' )[ 0 ];
+			if ( text.length === 0 ) {
 
-		var parserError = xml.getElementsByTagName( 'parsererror' )[ 0 ];
-		if ( parserError !== undefined ) {
+				return {
+					scene: new THREE.Scene()
+				};
 
-			// Chrome will return parser error with a div in it
+			}
 
-			var errorElement = getElementsByTagName( parserError, 'div' )[ 0 ];
-			var errorText;
+			var xml = new DOMParser().parseFromString( text, 'application/xml' );
+			var collada = getElementsByTagName( xml, 'COLLADA' )[ 0 ];
+			var parserError = xml.getElementsByTagName( 'parsererror' )[ 0 ];
 
-			if ( errorElement ) {
+			if ( parserError !== undefined ) {
 
-				errorText = errorElement.textContent;
+				// Chrome will return parser error with a div in it
+				var errorElement = getElementsByTagName( parserError, 'div' )[ 0 ];
+				var errorText;
 
-			} else {
+				if ( errorElement ) {
 
-				errorText = parserErrorToText( parserError );
+					errorText = errorElement.textContent;
 
-			}
+				} else {
 
-			console.error( 'THREE.ColladaLoader: Failed to parse collada file.\n', errorText );
+					errorText = parserErrorToText( parserError );
 
-			return null;
+				}
 
-		}
+				console.error( 'THREE.ColladaLoader: Failed to parse collada file.\n', errorText );
+				return null;
 
-		// metadata
+			} // metadata
 
-		var version = collada.getAttribute( 'version' );
-		console.log( 'THREE.ColladaLoader: File version', version );
 
-		var asset = parseAsset( getElementsByTagName( collada, 'asset' )[ 0 ] );
-		var textureLoader = new THREE.TextureLoader( this.manager );
-		textureLoader.setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin );
+			var version = collada.getAttribute( 'version' );
+			console.log( 'THREE.ColladaLoader: File version', version );
+			var asset = parseAsset( getElementsByTagName( collada, 'asset' )[ 0 ] );
+			var textureLoader = new THREE.TextureLoader( this.manager );
+			textureLoader.setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin );
+			var tgaLoader;
 
-		var tgaLoader;
+			if ( THREE.TGALoader ) {
 
-		if ( THREE.TGALoader ) {
+				tgaLoader = new THREE.TGALoader( this.manager );
+				tgaLoader.setPath( this.resourcePath || path );
 
-			tgaLoader = new THREE.TGALoader( this.manager );
-			tgaLoader.setPath( this.resourcePath || path );
+			} //
 
-		}
 
-		//
-
-		var animations = [];
-		var kinematics = {};
-		var count = 0;
-
-		//
-
-		var library = {
-			animations: {},
-			clips: {},
-			controllers: {},
-			images: {},
-			effects: {},
-			materials: {},
-			cameras: {},
-			lights: {},
-			geometries: {},
-			nodes: {},
-			visualScenes: {},
-			kinematicsModels: {},
-			physicsModels: {},
-			kinematicsScenes: {}
-		};
-
-		parseLibrary( collada, 'library_animations', 'animation', parseAnimation );
-		parseLibrary( collada, 'library_animation_clips', 'animation_clip', parseAnimationClip );
-		parseLibrary( collada, 'library_controllers', 'controller', parseController );
-		parseLibrary( collada, 'library_images', 'image', parseImage );
-		parseLibrary( collada, 'library_effects', 'effect', parseEffect );
-		parseLibrary( collada, 'library_materials', 'material', parseMaterial );
-		parseLibrary( collada, 'library_cameras', 'camera', parseCamera );
-		parseLibrary( collada, 'library_lights', 'light', parseLight );
-		parseLibrary( collada, 'library_geometries', 'geometry', parseGeometry );
-		parseLibrary( collada, 'library_nodes', 'node', parseNode );
-		parseLibrary( collada, 'library_visual_scenes', 'visual_scene', parseVisualScene );
-		parseLibrary( collada, 'library_kinematics_models', 'kinematics_model', parseKinematicsModel );
-		parseLibrary( collada, 'library_physics_models', 'physics_model', parsePhysicsModel );
-		parseLibrary( collada, 'scene', 'instance_kinematics_scene', parseKinematicsScene );
-
-		buildLibrary( library.animations, buildAnimation );
-		buildLibrary( library.clips, buildAnimationClip );
-		buildLibrary( library.controllers, buildController );
-		buildLibrary( library.images, buildImage );
-		buildLibrary( library.effects, buildEffect );
-		buildLibrary( library.materials, buildMaterial );
-		buildLibrary( library.cameras, buildCamera );
-		buildLibrary( library.lights, buildLight );
-		buildLibrary( library.geometries, buildGeometry );
-		buildLibrary( library.visualScenes, buildVisualScene );
-
-		setupAnimations();
-		setupKinematics();
-
-		var scene = parseScene( getElementsByTagName( collada, 'scene' )[ 0 ] );
-		scene.animations = animations;
-
-		if ( asset.upAxis === 'Z_UP' ) {
-
-			scene.quaternion.setFromEuler( new THREE.Euler( - Math.PI / 2, 0, 0 ) );
+			var animations = [];
+			var kinematics = {};
+			var count = 0; //
 
-		}
+			var library = {
+				animations: {},
+				clips: {},
+				controllers: {},
+				images: {},
+				effects: {},
+				materials: {},
+				cameras: {},
+				lights: {},
+				geometries: {},
+				nodes: {},
+				visualScenes: {},
+				kinematicsModels: {},
+				physicsModels: {},
+				kinematicsScenes: {}
+			};
+			parseLibrary( collada, 'library_animations', 'animation', parseAnimation );
+			parseLibrary( collada, 'library_animation_clips', 'animation_clip', parseAnimationClip );
+			parseLibrary( collada, 'library_controllers', 'controller', parseController );
+			parseLibrary( collada, 'library_images', 'image', parseImage );
+			parseLibrary( collada, 'library_effects', 'effect', parseEffect );
+			parseLibrary( collada, 'library_materials', 'material', parseMaterial );
+			parseLibrary( collada, 'library_cameras', 'camera', parseCamera );
+			parseLibrary( collada, 'library_lights', 'light', parseLight );
+			parseLibrary( collada, 'library_geometries', 'geometry', parseGeometry );
+			parseLibrary( collada, 'library_nodes', 'node', parseNode );
+			parseLibrary( collada, 'library_visual_scenes', 'visual_scene', parseVisualScene );
+			parseLibrary( collada, 'library_kinematics_models', 'kinematics_model', parseKinematicsModel );
+			parseLibrary( collada, 'library_physics_models', 'physics_model', parsePhysicsModel );
+			parseLibrary( collada, 'scene', 'instance_kinematics_scene', parseKinematicsScene );
+			buildLibrary( library.animations, buildAnimation );
+			buildLibrary( library.clips, buildAnimationClip );
+			buildLibrary( library.controllers, buildController );
+			buildLibrary( library.images, buildImage );
+			buildLibrary( library.effects, buildEffect );
+			buildLibrary( library.materials, buildMaterial );
+			buildLibrary( library.cameras, buildCamera );
+			buildLibrary( library.lights, buildLight );
+			buildLibrary( library.geometries, buildGeometry );
+			buildLibrary( library.visualScenes, buildVisualScene );
+			setupAnimations();
+			setupKinematics();
+			var scene = parseScene( getElementsByTagName( collada, 'scene' )[ 0 ] );
+			scene.animations = animations;
+
+			if ( asset.upAxis === 'Z_UP' ) {
+
+				scene.quaternion.setFromEuler( new THREE.Euler( - Math.PI / 2, 0, 0 ) );
+
+			}
+
+			scene.scale.multiplyScalar( asset.unit );
+			return {
+				get animations() {
 
-		scene.scale.multiplyScalar( asset.unit );
+					console.warn( 'THREE.ColladaLoader: Please access animations over scene.animations now.' );
+					return animations;
 
-		return {
-			get animations() {
+				},
 
-				console.warn( 'THREE.ColladaLoader: Please access animations over scene.animations now.' );
-				return animations;
+				kinematics: kinematics,
+				library: library,
+				scene: scene
+			};
 
-			},
-			kinematics: kinematics,
-			library: library,
-			scene: scene
-		};
+		}
+	} );
 
-	}
+	THREE.ColladaLoader = ColladaLoader;
 
-} );
+} )();

+ 187 - 208
examples/js/loaders/DDSLoader.js

@@ -1,270 +1,249 @@
-THREE.DDSLoader = function ( manager ) {
+( function () {
+
+	var DDSLoader = function ( manager ) {
+
+		THREE.CompressedTextureLoader.call( this, manager );
+
+	};
+
+	DDSLoader.prototype = Object.assign( Object.create( THREE.CompressedTextureLoader.prototype ), {
+		constructor: DDSLoader,
+		parse: function ( buffer, loadMipmaps ) {
+
+			var dds = {
+				mipmaps: [],
+				width: 0,
+				height: 0,
+				format: null,
+				mipmapCount: 1
+			}; // Adapted from @toji's DDS utils
+			// https://github.com/toji/webgl-texture-utils/blob/master/texture-util/dds.js
+			// All values and structures referenced from:
+			// http://msdn.microsoft.com/en-us/library/bb943991.aspx/
+
+			var DDS_MAGIC = 0x20534444; // var DDSD_CAPS = 0x1;
+			// var DDSD_HEIGHT = 0x2;
+			// var DDSD_WIDTH = 0x4;
+			// var DDSD_PITCH = 0x8;
+			// var DDSD_PIXELFORMAT = 0x1000;
+
+			var DDSD_MIPMAPCOUNT = 0x20000; // var DDSD_LINEARSIZE = 0x80000;
+			// var DDSD_DEPTH = 0x800000;
+			// var DDSCAPS_COMPLEX = 0x8;
+			// var DDSCAPS_MIPMAP = 0x400000;
+			// var DDSCAPS_TEXTURE = 0x1000;
+
+			var DDSCAPS2_CUBEMAP = 0x200;
+			var DDSCAPS2_CUBEMAP_POSITIVEX = 0x400;
+			var DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800;
+			var DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000;
+			var DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000;
+			var DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000;
+			var DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000; // var DDSCAPS2_VOLUME = 0x200000;
+			// var DDPF_ALPHAPIXELS = 0x1;
+			// var DDPF_ALPHA = 0x2;
+
+			var DDPF_FOURCC = 0x4; // var DDPF_RGB = 0x40;
+			// var DDPF_YUV = 0x200;
+			// var DDPF_LUMINANCE = 0x20000;
+
+			function fourCCToInt32( value ) {
+
+				return value.charCodeAt( 0 ) + ( value.charCodeAt( 1 ) << 8 ) + ( value.charCodeAt( 2 ) << 16 ) + ( value.charCodeAt( 3 ) << 24 );
 
-	THREE.CompressedTextureLoader.call( this, manager );
-
-};
-
-THREE.DDSLoader.prototype = Object.assign( Object.create( THREE.CompressedTextureLoader.prototype ), {
-
-	constructor: THREE.DDSLoader,
-
-	parse: function ( buffer, loadMipmaps ) {
-
-		var dds = { mipmaps: [], width: 0, height: 0, format: null, mipmapCount: 1 };
-
-		// Adapted from @toji's DDS utils
-		// https://github.com/toji/webgl-texture-utils/blob/master/texture-util/dds.js
-
-		// All values and structures referenced from:
-		// http://msdn.microsoft.com/en-us/library/bb943991.aspx/
+			}
 
-		var DDS_MAGIC = 0x20534444;
+			function int32ToFourCC( value ) {
 
-		// var DDSD_CAPS = 0x1;
-		// var DDSD_HEIGHT = 0x2;
-		// var DDSD_WIDTH = 0x4;
-		// var DDSD_PITCH = 0x8;
-		// var DDSD_PIXELFORMAT = 0x1000;
-		var DDSD_MIPMAPCOUNT = 0x20000;
-		// var DDSD_LINEARSIZE = 0x80000;
-		// var DDSD_DEPTH = 0x800000;
+				return String.fromCharCode( value & 0xff, value >> 8 & 0xff, value >> 16 & 0xff, value >> 24 & 0xff );
 
-		// var DDSCAPS_COMPLEX = 0x8;
-		// var DDSCAPS_MIPMAP = 0x400000;
-		// var DDSCAPS_TEXTURE = 0x1000;
+			}
 
-		var DDSCAPS2_CUBEMAP = 0x200;
-		var DDSCAPS2_CUBEMAP_POSITIVEX = 0x400;
-		var DDSCAPS2_CUBEMAP_NEGATIVEX = 0x800;
-		var DDSCAPS2_CUBEMAP_POSITIVEY = 0x1000;
-		var DDSCAPS2_CUBEMAP_NEGATIVEY = 0x2000;
-		var DDSCAPS2_CUBEMAP_POSITIVEZ = 0x4000;
-		var DDSCAPS2_CUBEMAP_NEGATIVEZ = 0x8000;
-		// var DDSCAPS2_VOLUME = 0x200000;
+			function loadARGBMip( buffer, dataOffset, width, height ) {
 
-		// var DDPF_ALPHAPIXELS = 0x1;
-		// var DDPF_ALPHA = 0x2;
-		var DDPF_FOURCC = 0x4;
-		// var DDPF_RGB = 0x40;
-		// var DDPF_YUV = 0x200;
-		// var DDPF_LUMINANCE = 0x20000;
+				var dataLength = width * height * 4;
+				var srcBuffer = new Uint8Array( buffer, dataOffset, dataLength );
+				var byteArray = new Uint8Array( dataLength );
+				var dst = 0;
+				var src = 0;
 
-		function fourCCToInt32( value ) {
+				for ( var y = 0; y < height; y ++ ) {
 
-			return value.charCodeAt( 0 ) +
-				( value.charCodeAt( 1 ) << 8 ) +
-				( value.charCodeAt( 2 ) << 16 ) +
-				( value.charCodeAt( 3 ) << 24 );
+					for ( var x = 0; x < width; x ++ ) {
 
-		}
+						var b = srcBuffer[ src ];
+						src ++;
+						var g = srcBuffer[ src ];
+						src ++;
+						var r = srcBuffer[ src ];
+						src ++;
+						var a = srcBuffer[ src ];
+						src ++;
+						byteArray[ dst ] = r;
+						dst ++; //r
 
-		function int32ToFourCC( value ) {
+						byteArray[ dst ] = g;
+						dst ++; //g
 
-			return String.fromCharCode(
-				value & 0xff,
-				( value >> 8 ) & 0xff,
-				( value >> 16 ) & 0xff,
-				( value >> 24 ) & 0xff
-			);
+						byteArray[ dst ] = b;
+						dst ++; //b
 
-		}
+						byteArray[ dst ] = a;
+						dst ++; //a
 
-		function loadARGBMip( buffer, dataOffset, width, height ) {
+					}
 
-			var dataLength = width * height * 4;
-			var srcBuffer = new Uint8Array( buffer, dataOffset, dataLength );
-			var byteArray = new Uint8Array( dataLength );
-			var dst = 0;
-			var src = 0;
-			for ( var y = 0; y < height; y ++ ) {
+				}
 
-				for ( var x = 0; x < width; x ++ ) {
+				return byteArray;
 
-					var b = srcBuffer[ src ]; src ++;
-					var g = srcBuffer[ src ]; src ++;
-					var r = srcBuffer[ src ]; src ++;
-					var a = srcBuffer[ src ]; src ++;
-					byteArray[ dst ] = r; dst ++;	//r
-					byteArray[ dst ] = g; dst ++;	//g
-					byteArray[ dst ] = b; dst ++;	//b
-					byteArray[ dst ] = a; dst ++;	//a
+			}
 
-				}
+			var FOURCC_DXT1 = fourCCToInt32( 'DXT1' );
+			var FOURCC_DXT3 = fourCCToInt32( 'DXT3' );
+			var FOURCC_DXT5 = fourCCToInt32( 'DXT5' );
+			var FOURCC_ETC1 = fourCCToInt32( 'ETC1' );
+			var headerLengthInt = 31; // The header length in 32 bit ints
+			// Offsets into the header array
+
+			var off_magic = 0;
+			var off_size = 1;
+			var off_flags = 2;
+			var off_height = 3;
+			var off_width = 4;
+			var off_mipmapCount = 7;
+			var off_pfFlags = 20;
+			var off_pfFourCC = 21;
+			var off_RGBBitCount = 22;
+			var off_RBitMask = 23;
+			var off_GBitMask = 24;
+			var off_BBitMask = 25;
+			var off_ABitMask = 26; // var off_caps = 27;
+
+			var off_caps2 = 28; // var off_caps3 = 29;
+			// var off_caps4 = 30;
+			// Parse header
+
+			var header = new Int32Array( buffer, 0, headerLengthInt );
+
+			if ( header[ off_magic ] !== DDS_MAGIC ) {
+
+				console.error( 'THREE.DDSLoader.parse: Invalid magic number in DDS header.' );
+				return dds;
 
 			}
 
-			return byteArray;
+			if ( ! header[ off_pfFlags ] & DDPF_FOURCC ) {
 
-		}
+				console.error( 'THREE.DDSLoader.parse: Unsupported format, must contain a FourCC code.' );
+				return dds;
 
-		var FOURCC_DXT1 = fourCCToInt32( 'DXT1' );
-		var FOURCC_DXT3 = fourCCToInt32( 'DXT3' );
-		var FOURCC_DXT5 = fourCCToInt32( 'DXT5' );
-		var FOURCC_ETC1 = fourCCToInt32( 'ETC1' );
+			}
 
-		var headerLengthInt = 31; // The header length in 32 bit ints
+			var blockBytes;
+			var fourCC = header[ off_pfFourCC ];
+			var isRGBAUncompressed = false;
 
-		// Offsets into the header array
+			switch ( fourCC ) {
 
-		var off_magic = 0;
+				case FOURCC_DXT1:
+					blockBytes = 8;
+					dds.format = THREE.RGB_S3TC_DXT1_Format;
+					break;
 
-		var off_size = 1;
-		var off_flags = 2;
-		var off_height = 3;
-		var off_width = 4;
+				case FOURCC_DXT3:
+					blockBytes = 16;
+					dds.format = THREE.RGBA_S3TC_DXT3_Format;
+					break;
 
-		var off_mipmapCount = 7;
+				case FOURCC_DXT5:
+					blockBytes = 16;
+					dds.format = THREE.RGBA_S3TC_DXT5_Format;
+					break;
 
-		var off_pfFlags = 20;
-		var off_pfFourCC = 21;
-		var off_RGBBitCount = 22;
-		var off_RBitMask = 23;
-		var off_GBitMask = 24;
-		var off_BBitMask = 25;
-		var off_ABitMask = 26;
+				case FOURCC_ETC1:
+					blockBytes = 8;
+					dds.format = THREE.RGB_ETC1_Format;
+					break;
 
-		// var off_caps = 27;
-		var off_caps2 = 28;
-		// var off_caps3 = 29;
-		// var off_caps4 = 30;
+				default:
+					if ( header[ off_RGBBitCount ] === 32 && header[ off_RBitMask ] & 0xff0000 && header[ off_GBitMask ] & 0xff00 && header[ off_BBitMask ] & 0xff && header[ off_ABitMask ] & 0xff000000 ) {
 
-		// Parse header
+						isRGBAUncompressed = true;
+						blockBytes = 64;
+						dds.format = THREE.RGBAFormat;
 
-		var header = new Int32Array( buffer, 0, headerLengthInt );
+					} else {
 
-		if ( header[ off_magic ] !== DDS_MAGIC ) {
+						console.error( 'THREE.DDSLoader.parse: Unsupported FourCC code ', int32ToFourCC( fourCC ) );
+						return dds;
 
-			console.error( 'THREE.DDSLoader.parse: Invalid magic number in DDS header.' );
-			return dds;
+					}
 
-		}
+			}
 
-		if ( ! header[ off_pfFlags ] & DDPF_FOURCC ) {
+			dds.mipmapCount = 1;
 
-			console.error( 'THREE.DDSLoader.parse: Unsupported format, must contain a FourCC code.' );
-			return dds;
+			if ( header[ off_flags ] & DDSD_MIPMAPCOUNT && loadMipmaps !== false ) {
 
-		}
-
-		var blockBytes;
+				dds.mipmapCount = Math.max( 1, header[ off_mipmapCount ] );
 
-		var fourCC = header[ off_pfFourCC ];
+			}
 
-		var isRGBAUncompressed = false;
+			var caps2 = header[ off_caps2 ];
+			dds.isCubemap = caps2 & DDSCAPS2_CUBEMAP ? true : false;
 
-		switch ( fourCC ) {
+			if ( dds.isCubemap && ( ! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEX ) || ! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEX ) || ! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEY ) || ! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEY ) || ! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEZ ) || ! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEZ ) ) ) {
 
-			case FOURCC_DXT1:
+				console.error( 'THREE.DDSLoader.parse: Incomplete cubemap faces' );
+				return dds;
 
-				blockBytes = 8;
-				dds.format = THREE.RGB_S3TC_DXT1_Format;
-				break;
+			}
 
-			case FOURCC_DXT3:
+			dds.width = header[ off_width ];
+			dds.height = header[ off_height ];
+			var dataOffset = header[ off_size ] + 4; // Extract mipmaps buffers
 
-				blockBytes = 16;
-				dds.format = THREE.RGBA_S3TC_DXT3_Format;
-				break;
+			var faces = dds.isCubemap ? 6 : 1;
 
-			case FOURCC_DXT5:
+			for ( var face = 0; face < faces; face ++ ) {
 
-				blockBytes = 16;
-				dds.format = THREE.RGBA_S3TC_DXT5_Format;
-				break;
+				var width = dds.width;
+				var height = dds.height;
 
-			case FOURCC_ETC1:
+				for ( var i = 0; i < dds.mipmapCount; i ++ ) {
 
-				blockBytes = 8;
-				dds.format = THREE.RGB_ETC1_Format;
-				break;
+					if ( isRGBAUncompressed ) {
 
-			default:
+						var byteArray = loadARGBMip( buffer, dataOffset, width, height );
+						var dataLength = byteArray.length;
 
-				if ( header[ off_RGBBitCount ] === 32
-					&& header[ off_RBitMask ] & 0xff0000
-					&& header[ off_GBitMask ] & 0xff00
-					&& header[ off_BBitMask ] & 0xff
-					&& header[ off_ABitMask ] & 0xff000000 ) {
+					} else {
 
-					isRGBAUncompressed = true;
-					blockBytes = 64;
-					dds.format = THREE.RGBAFormat;
+						var dataLength = Math.max( 4, width ) / 4 * Math.max( 4, height ) / 4 * blockBytes;
+						var byteArray = new Uint8Array( buffer, dataOffset, dataLength );
 
-				} else {
+					}
 
-					console.error( 'THREE.DDSLoader.parse: Unsupported FourCC code ', int32ToFourCC( fourCC ) );
-					return dds;
+					var mipmap = {
+						'data': byteArray,
+						'width': width,
+						'height': height
+					};
+					dds.mipmaps.push( mipmap );
+					dataOffset += dataLength;
+					width = Math.max( width >> 1, 1 );
+					height = Math.max( height >> 1, 1 );
 
 				}
 
-		}
-
-		dds.mipmapCount = 1;
-
-		if ( header[ off_flags ] & DDSD_MIPMAPCOUNT && loadMipmaps !== false ) {
-
-			dds.mipmapCount = Math.max( 1, header[ off_mipmapCount ] );
-
-		}
+			}
 
-		var caps2 = header[ off_caps2 ];
-		dds.isCubemap = caps2 & DDSCAPS2_CUBEMAP ? true : false;
-		if ( dds.isCubemap && (
-			! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEX ) ||
-			! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEX ) ||
-			! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEY ) ||
-			! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEY ) ||
-			! ( caps2 & DDSCAPS2_CUBEMAP_POSITIVEZ ) ||
-			! ( caps2 & DDSCAPS2_CUBEMAP_NEGATIVEZ )
-		) ) {
-
-			console.error( 'THREE.DDSLoader.parse: Incomplete cubemap faces' );
 			return dds;
 
 		}
+	} );
 
-		dds.width = header[ off_width ];
-		dds.height = header[ off_height ];
-
-		var dataOffset = header[ off_size ] + 4;
-
-		// Extract mipmaps buffers
-
-		var faces = dds.isCubemap ? 6 : 1;
-
-		for ( var face = 0; face < faces; face ++ ) {
-
-			var width = dds.width;
-			var height = dds.height;
-
-			for ( var i = 0; i < dds.mipmapCount; i ++ ) {
-
-				if ( isRGBAUncompressed ) {
-
-					var byteArray = loadARGBMip( buffer, dataOffset, width, height );
-					var dataLength = byteArray.length;
-
-				} else {
-
-					var dataLength = Math.max( 4, width ) / 4 * Math.max( 4, height ) / 4 * blockBytes;
-					var byteArray = new Uint8Array( buffer, dataOffset, dataLength );
-
-				}
-
-				var mipmap = { 'data': byteArray, 'width': width, 'height': height };
-				dds.mipmaps.push( mipmap );
-
-				dataOffset += dataLength;
-
-				width = Math.max( width >> 1, 1 );
-				height = Math.max( height >> 1, 1 );
-
-			}
-
-		}
-
-		return dds;
-
-	}
+	THREE.DDSLoader = DDSLoader;
 
-} );
+} )();

+ 383 - 405
examples/js/loaders/DRACOLoader.js

@@ -1,289 +1,250 @@
-THREE.DRACOLoader = function ( manager ) {
-
-	THREE.Loader.call( this, manager );
-
-	this.decoderPath = '';
-	this.decoderConfig = {};
-	this.decoderBinary = null;
-	this.decoderPending = null;
-
-	this.workerLimit = 4;
-	this.workerPool = [];
-	this.workerNextTaskID = 1;
-	this.workerSourceURL = '';
+( function () {
+
+	var DRACOLoader = function ( manager ) {
+
+		THREE.Loader.call( this, manager );
+		this.decoderPath = '';
+		this.decoderConfig = {};
+		this.decoderBinary = null;
+		this.decoderPending = null;
+		this.workerLimit = 4;
+		this.workerPool = [];
+		this.workerNextTaskID = 1;
+		this.workerSourceURL = '';
+		this.defaultAttributeIDs = {
+			position: 'POSITION',
+			normal: 'NORMAL',
+			color: 'COLOR',
+			uv: 'TEX_COORD'
+		};
+		this.defaultAttributeTypes = {
+			position: 'Float32Array',
+			normal: 'Float32Array',
+			color: 'Float32Array',
+			uv: 'Float32Array'
+		};
 
-	this.defaultAttributeIDs = {
-		position: 'POSITION',
-		normal: 'NORMAL',
-		color: 'COLOR',
-		uv: 'TEX_COORD'
-	};
-	this.defaultAttributeTypes = {
-		position: 'Float32Array',
-		normal: 'Float32Array',
-		color: 'Float32Array',
-		uv: 'Float32Array'
 	};
 
-};
-
-THREE.DRACOLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+	DRACOLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+		constructor: DRACOLoader,
+		setDecoderPath: function ( path ) {
 
-	constructor: THREE.DRACOLoader,
+			this.decoderPath = path;
+			return this;
 
-	setDecoderPath: function ( path ) {
+		},
+		setDecoderConfig: function ( config ) {
 
-		this.decoderPath = path;
+			this.decoderConfig = config;
+			return this;
 
-		return this;
+		},
+		setWorkerLimit: function ( workerLimit ) {
 
-	},
+			this.workerLimit = workerLimit;
+			return this;
 
-	setDecoderConfig: function ( config ) {
+		},
 
-		this.decoderConfig = config;
+		/** @deprecated */
+		setVerbosity: function () {
 
-		return this;
+			console.warn( 'THREE.DRACOLoader: The .setVerbosity() method has been removed.' );
 
-	},
+		},
 
-	setWorkerLimit: function ( workerLimit ) {
+		/** @deprecated */
+		setDrawMode: function () {
 
-		this.workerLimit = workerLimit;
+			console.warn( 'THREE.DRACOLoader: The .setDrawMode() method has been removed.' );
 
-		return this;
+		},
 
-	},
+		/** @deprecated */
+		setSkipDequantization: function () {
 
-	/** @deprecated */
-	setVerbosity: function () {
-
-		console.warn( 'THREE.DRACOLoader: The .setVerbosity() method has been removed.' );
+			console.warn( 'THREE.DRACOLoader: The .setSkipDequantization() method has been removed.' );
 
-	},
-
-	/** @deprecated */
-	setDrawMode: function () {
+		},
+		load: function ( url, onLoad, onProgress, onError ) {
 
-		console.warn( 'THREE.DRACOLoader: The .setDrawMode() method has been removed.' );
-
-	},
-
-	/** @deprecated */
-	setSkipDequantization: function () {
+			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, buffer => {
 
-		console.warn( 'THREE.DRACOLoader: The .setSkipDequantization() method has been removed.' );
-
-	},
-
-	load: function ( url, onLoad, onProgress, onError ) {
+				var taskConfig = {
+					attributeIDs: this.defaultAttributeIDs,
+					attributeTypes: this.defaultAttributeTypes,
+					useUniqueIDs: false
+				};
+				this.decodeGeometry( buffer, taskConfig ).then( onLoad ).catch( onError );
 
-		var loader = new THREE.FileLoader( this.manager );
+			}, onProgress, onError );
 
-		loader.setPath( this.path );
-		loader.setResponseType( 'arraybuffer' );
-		loader.setRequestHeader( this.requestHeader );
-		loader.setWithCredentials( this.withCredentials );
+		},
 
-		loader.load( url, ( buffer ) => {
+		/** @deprecated Kept for backward-compatibility with previous DRACOLoader versions. */
+		decodeDracoFile: function ( buffer, callback, attributeIDs, attributeTypes ) {
 
 			var taskConfig = {
-				attributeIDs: this.defaultAttributeIDs,
-				attributeTypes: this.defaultAttributeTypes,
-				useUniqueIDs: false
+				attributeIDs: attributeIDs || this.defaultAttributeIDs,
+				attributeTypes: attributeTypes || this.defaultAttributeTypes,
+				useUniqueIDs: !! attributeIDs
 			};
+			this.decodeGeometry( buffer, taskConfig ).then( callback );
 
-			this.decodeGeometry( buffer, taskConfig )
-				.then( onLoad )
-				.catch( onError );
-
-		}, onProgress, onError );
-
-	},
-
-	/** @deprecated Kept for backward-compatibility with previous DRACOLoader versions. */
-	decodeDracoFile: function ( buffer, callback, attributeIDs, attributeTypes ) {
-
-		var taskConfig = {
-			attributeIDs: attributeIDs || this.defaultAttributeIDs,
-			attributeTypes: attributeTypes || this.defaultAttributeTypes,
-			useUniqueIDs: !! attributeIDs
-		};
-
-		this.decodeGeometry( buffer, taskConfig ).then( callback );
-
-	},
-
-	decodeGeometry: function ( buffer, taskConfig ) {
-
-		// TODO: For backward-compatibility, support 'attributeTypes' objects containing
-		// references (rather than names) to typed array constructors. These must be
-		// serialized before sending them to the worker.
-		for ( var attribute in taskConfig.attributeTypes ) {
-
-			var type = taskConfig.attributeTypes[ attribute ];
+		},
+		decodeGeometry: function ( buffer, taskConfig ) {
 
-			if ( type.BYTES_PER_ELEMENT !== undefined ) {
+			// TODO: For backward-compatibility, support 'attributeTypes' objects containing
+			// references (rather than names) to typed array constructors. These must be
+			// serialized before sending them to the worker.
+			for ( var attribute in taskConfig.attributeTypes ) {
 
-				taskConfig.attributeTypes[ attribute ] = type.name;
+				var type = taskConfig.attributeTypes[ attribute ];
 
-			}
+				if ( type.BYTES_PER_ELEMENT !== undefined ) {
 
-		}
+					taskConfig.attributeTypes[ attribute ] = type.name;
 
-		//
+				}
 
-		var taskKey = JSON.stringify( taskConfig );
+			} //
 
-		// Check for an existing task using this buffer. A transferred buffer cannot be transferred
-		// again from this thread.
-		if ( THREE.DRACOLoader.taskCache.has( buffer ) ) {
 
-			var cachedTask = THREE.DRACOLoader.taskCache.get( buffer );
+			var taskKey = JSON.stringify( taskConfig ); // Check for an existing task using this buffer. A transferred buffer cannot be transferred
+			// again from this thread.
 
-			if ( cachedTask.key === taskKey ) {
+			if ( DRACOLoader.taskCache.has( buffer ) ) {
 
-				return cachedTask.promise;
+				var cachedTask = DRACOLoader.taskCache.get( buffer );
 
-			} else if ( buffer.byteLength === 0 ) {
+				if ( cachedTask.key === taskKey ) {
 
-				// Technically, it would be possible to wait for the previous task to complete,
-				// transfer the buffer back, and decode again with the second configuration. That
-				// is complex, and I don't know of any reason to decode a Draco buffer twice in
-				// different ways, so this is left unimplemented.
-				throw new Error(
+					return cachedTask.promise;
 
-					'THREE.DRACOLoader: Unable to re-decode a buffer with different ' +
-					'settings. Buffer has already been transferred.'
+				} else if ( buffer.byteLength === 0 ) {
 
-				);
+					// Technically, it would be possible to wait for the previous task to complete,
+					// transfer the buffer back, and decode again with the second configuration. That
+					// is complex, and I don't know of any reason to decode a Draco buffer twice in
+					// different ways, so this is left unimplemented.
+					throw new Error( 'THREE.DRACOLoader: Unable to re-decode a buffer with different ' + 'settings. Buffer has already been transferred.' );
 
-			}
+				}
 
-		}
+			} //
 
-		//
 
-		var worker;
-		var taskID = this.workerNextTaskID ++;
-		var taskCost = buffer.byteLength;
+			var worker;
+			var taskID = this.workerNextTaskID ++;
+			var taskCost = buffer.byteLength; // Obtain a worker and assign a task, and construct a geometry instance
+			// when the task completes.
 
-		// Obtain a worker and assign a task, and construct a geometry instance
-		// when the task completes.
-		var geometryPending = this._getWorker( taskID, taskCost )
-			.then( ( _worker ) => {
+			var geometryPending = this._getWorker( taskID, taskCost ).then( _worker => {
 
 				worker = _worker;
-
 				return new Promise( ( resolve, reject ) => {
 
-					worker._callbacks[ taskID ] = { resolve, reject };
-
-					worker.postMessage( { type: 'decode', id: taskID, taskConfig, buffer }, [ buffer ] );
-
-					// this.debug();
+					worker._callbacks[ taskID ] = {
+						resolve,
+						reject
+					};
+					worker.postMessage( {
+						type: 'decode',
+						id: taskID,
+						taskConfig,
+						buffer
+					}, [ buffer ] ); // this.debug();
 
 				} );
 
-			} )
-			.then( ( message ) => this._createGeometry( message.geometry ) );
+			} ).then( message => this._createGeometry( message.geometry ) ); // Remove task from the task list.
+			// Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416)
 
-		// Remove task from the task list.
-		// Note: replaced '.finally()' with '.catch().then()' block - iOS 11 support (#19416)
-		geometryPending
-			.catch( () => true )
-			.then( () => {
 
-				if ( worker && taskID ) {
+			geometryPending.catch( () => true ).then( () => {
 
-					this._releaseTask( worker, taskID );
+				if ( worker && taskID ) {
 
-					// this.debug();
+					this._releaseTask( worker, taskID ); // this.debug();
 
 				}
 
-			} );
-
-		// Cache the task result.
-		THREE.DRACOLoader.taskCache.set( buffer, {
+			} ); // Cache the task result.
 
-			key: taskKey,
-			promise: geometryPending
-
-		} );
-
-		return geometryPending;
-
-	},
-
-	_createGeometry: function ( geometryData ) {
-
-		var geometry = new THREE.BufferGeometry();
-
-		if ( geometryData.index ) {
-
-			geometry.setIndex( new THREE.BufferAttribute( geometryData.index.array, 1 ) );
-
-		}
-
-		for ( var i = 0; i < geometryData.attributes.length; i ++ ) {
+			DRACOLoader.taskCache.set( buffer, {
+				key: taskKey,
+				promise: geometryPending
+			} );
+			return geometryPending;
 
-			var attribute = geometryData.attributes[ i ];
-			var name = attribute.name;
-			var array = attribute.array;
-			var itemSize = attribute.itemSize;
+		},
+		_createGeometry: function ( geometryData ) {
 
-			geometry.setAttribute( name, new THREE.BufferAttribute( array, itemSize ) );
+			var geometry = new THREE.BufferGeometry();
 
-		}
+			if ( geometryData.index ) {
 
-		return geometry;
+				geometry.setIndex( new THREE.BufferAttribute( geometryData.index.array, 1 ) );
 
-	},
+			}
 
-	_loadLibrary: function ( url, responseType ) {
+			for ( var i = 0; i < geometryData.attributes.length; i ++ ) {
 
-		var loader = new THREE.FileLoader( this.manager );
-		loader.setPath( this.decoderPath );
-		loader.setResponseType( responseType );
-		loader.setWithCredentials( this.withCredentials );
+				var attribute = geometryData.attributes[ i ];
+				var name = attribute.name;
+				var array = attribute.array;
+				var itemSize = attribute.itemSize;
+				geometry.setAttribute( name, new THREE.BufferAttribute( array, itemSize ) );
 
-		return new Promise( ( resolve, reject ) => {
+			}
 
-			loader.load( url, resolve, undefined, reject );
+			return geometry;
 
-		} );
+		},
+		_loadLibrary: function ( url, responseType ) {
 
-	},
+			var loader = new THREE.FileLoader( this.manager );
+			loader.setPath( this.decoderPath );
+			loader.setResponseType( responseType );
+			loader.setWithCredentials( this.withCredentials );
+			return new Promise( ( resolve, reject ) => {
 
-	preload: function () {
+				loader.load( url, resolve, undefined, reject );
 
-		this._initDecoder();
+			} );
 
-		return this;
+		},
+		preload: function () {
 
-	},
+			this._initDecoder();
 
-	_initDecoder: function () {
+			return this;
 
-		if ( this.decoderPending ) return this.decoderPending;
+		},
+		_initDecoder: function () {
 
-		var useJS = typeof WebAssembly !== 'object' || this.decoderConfig.type === 'js';
-		var librariesPending = [];
+			if ( this.decoderPending ) return this.decoderPending;
+			var useJS = typeof WebAssembly !== 'object' || this.decoderConfig.type === 'js';
+			var librariesPending = [];
 
-		if ( useJS ) {
+			if ( useJS ) {
 
-			librariesPending.push( this._loadLibrary( 'draco_decoder.js', 'text' ) );
+				librariesPending.push( this._loadLibrary( 'draco_decoder.js', 'text' ) );
 
-		} else {
+			} else {
 
-			librariesPending.push( this._loadLibrary( 'draco_wasm_wrapper.js', 'text' ) );
-			librariesPending.push( this._loadLibrary( 'draco_decoder.wasm', 'arraybuffer' ) );
+				librariesPending.push( this._loadLibrary( 'draco_wasm_wrapper.js', 'text' ) );
+				librariesPending.push( this._loadLibrary( 'draco_decoder.wasm', 'arraybuffer' ) );
 
-		}
+			}
 
-		this.decoderPending = Promise.all( librariesPending )
-			.then( ( libraries ) => {
+			this.decoderPending = Promise.all( librariesPending ).then( libraries => {
 
 				var jsContent = libraries[ 0 ];
 
@@ -293,339 +254,356 @@ THREE.DRACOLoader.prototype = Object.assign( Object.create( THREE.Loader.prototy
 
 				}
 
-				var fn = THREE.DRACOLoader.DRACOWorker.toString();
-
-				var body = [
-					'/* draco decoder */',
-					jsContent,
-					'',
-					'/* worker */',
-					fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) )
-				].join( '\n' );
-
+				var fn = DRACOLoader.DRACOWorker.toString();
+				var body = [ '/* draco decoder */', jsContent, '', '/* worker */', fn.substring( fn.indexOf( '{' ) + 1, fn.lastIndexOf( '}' ) ) ].join( '\n' );
 				this.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) );
 
 			} );
+			return this.decoderPending;
 
-		return this.decoderPending;
+		},
+		_getWorker: function ( taskID, taskCost ) {
 
-	},
+			return this._initDecoder().then( () => {
 
-	_getWorker: function ( taskID, taskCost ) {
+				if ( this.workerPool.length < this.workerLimit ) {
 
-		return this._initDecoder().then( () => {
+					var worker = new Worker( this.workerSourceURL );
+					worker._callbacks = {};
+					worker._taskCosts = {};
+					worker._taskLoad = 0;
+					worker.postMessage( {
+						type: 'init',
+						decoderConfig: this.decoderConfig
+					} );
 
-			if ( this.workerPool.length < this.workerLimit ) {
+					worker.onmessage = function ( e ) {
 
-				var worker = new Worker( this.workerSourceURL );
+						var message = e.data;
 
-				worker._callbacks = {};
-				worker._taskCosts = {};
-				worker._taskLoad = 0;
+						switch ( message.type ) {
 
-				worker.postMessage( { type: 'init', decoderConfig: this.decoderConfig } );
+							case 'decode':
+								worker._callbacks[ message.id ].resolve( message );
 
-				worker.onmessage = function ( e ) {
+								break;
 
-					var message = e.data;
+							case 'error':
+								worker._callbacks[ message.id ].reject( message );
 
-					switch ( message.type ) {
+								break;
 
-						case 'decode':
-							worker._callbacks[ message.id ].resolve( message );
-							break;
+							default:
+								console.error( 'THREE.DRACOLoader: Unexpected message, "' + message.type + '"' );
 
-						case 'error':
-							worker._callbacks[ message.id ].reject( message );
-							break;
+						}
 
-						default:
-							console.error( 'THREE.DRACOLoader: Unexpected message, "' + message.type + '"' );
+					};
 
-					}
+					this.workerPool.push( worker );
 
-				};
+				} else {
 
-				this.workerPool.push( worker );
+					this.workerPool.sort( function ( a, b ) {
 
-			} else {
+						return a._taskLoad > b._taskLoad ? - 1 : 1;
 
-				this.workerPool.sort( function ( a, b ) {
+					} );
 
-					return a._taskLoad > b._taskLoad ? - 1 : 1;
+				}
 
-				} );
+				var worker = this.workerPool[ this.workerPool.length - 1 ];
+				worker._taskCosts[ taskID ] = taskCost;
+				worker._taskLoad += taskCost;
+				return worker;
 
-			}
+			} );
 
-			var worker = this.workerPool[ this.workerPool.length - 1 ];
-			worker._taskCosts[ taskID ] = taskCost;
-			worker._taskLoad += taskCost;
-			return worker;
+		},
+		_releaseTask: function ( worker, taskID ) {
 
-		} );
+			worker._taskLoad -= worker._taskCosts[ taskID ];
+			delete worker._callbacks[ taskID ];
+			delete worker._taskCosts[ taskID ];
 
-	},
+		},
+		debug: function () {
 
-	_releaseTask: function ( worker, taskID ) {
+			console.log( 'Task load: ', this.workerPool.map( worker => worker._taskLoad ) );
 
-		worker._taskLoad -= worker._taskCosts[ taskID ];
-		delete worker._callbacks[ taskID ];
-		delete worker._taskCosts[ taskID ];
+		},
+		dispose: function () {
 
-	},
+			for ( var i = 0; i < this.workerPool.length; ++ i ) {
 
-	debug: function () {
+				this.workerPool[ i ].terminate();
 
-		console.log( 'Task load: ', this.workerPool.map( ( worker ) => worker._taskLoad ) );
+			}
 
-	},
+			this.workerPool.length = 0;
+			return this;
 
-	dispose: function () {
+		}
+	} );
+	/* WEB WORKER */
 
-		for ( var i = 0; i < this.workerPool.length; ++ i ) {
+	DRACOLoader.DRACOWorker = function () {
 
-			this.workerPool[ i ].terminate();
+		var decoderConfig;
+		var decoderPending;
 
-		}
+		onmessage = function ( e ) {
 
-		this.workerPool.length = 0;
+			var message = e.data;
 
-		return this;
+			switch ( message.type ) {
 
-	}
+				case 'init':
+					decoderConfig = message.decoderConfig;
+					decoderPending = new Promise( function ( resolve
+						/*, reject*/
+					) {
 
-} );
+						decoderConfig.onModuleLoaded = function ( draco ) {
 
-/* WEB WORKER */
+							// Module is Promise-like. Wrap before resolving to avoid loop.
+							resolve( {
+								draco: draco
+							} );
 
-THREE.DRACOLoader.DRACOWorker = function () {
+						};
 
-	var decoderConfig;
-	var decoderPending;
+						DracoDecoderModule( decoderConfig ); // eslint-disable-line no-undef
 
-	onmessage = function ( e ) {
+					} );
+					break;
 
-		var message = e.data;
+				case 'decode':
+					var buffer = message.buffer;
+					var taskConfig = message.taskConfig;
+					decoderPending.then( module => {
 
-		switch ( message.type ) {
+						var draco = module.draco;
+						var decoder = new draco.Decoder();
+						var decoderBuffer = new draco.DecoderBuffer();
+						decoderBuffer.Init( new Int8Array( buffer ), buffer.byteLength );
 
-			case 'init':
-				decoderConfig = message.decoderConfig;
-				decoderPending = new Promise( function ( resolve/*, reject*/ ) {
+						try {
 
-					decoderConfig.onModuleLoaded = function ( draco ) {
+							var geometry = decodeGeometry( draco, decoder, decoderBuffer, taskConfig );
+							var buffers = geometry.attributes.map( attr => attr.array.buffer );
+							if ( geometry.index ) buffers.push( geometry.index.array.buffer );
+							self.postMessage( {
+								type: 'decode',
+								id: message.id,
+								geometry
+							}, buffers );
 
-						// Module is Promise-like. Wrap before resolving to avoid loop.
-						resolve( { draco: draco } );
+						} catch ( error ) {
 
-					};
+							console.error( error );
+							self.postMessage( {
+								type: 'error',
+								id: message.id,
+								error: error.message
+							} );
 
-					DracoDecoderModule( decoderConfig ); // eslint-disable-line no-undef
+						} finally {
 
-				} );
-				break;
+							draco.destroy( decoderBuffer );
+							draco.destroy( decoder );
 
-			case 'decode':
-				var buffer = message.buffer;
-				var taskConfig = message.taskConfig;
-				decoderPending.then( ( module ) => {
+						}
 
-					var draco = module.draco;
-					var decoder = new draco.Decoder();
-					var decoderBuffer = new draco.DecoderBuffer();
-					decoderBuffer.Init( new Int8Array( buffer ), buffer.byteLength );
+					} );
+					break;
 
-					try {
+			}
 
-						var geometry = decodeGeometry( draco, decoder, decoderBuffer, taskConfig );
+		};
 
-						var buffers = geometry.attributes.map( ( attr ) => attr.array.buffer );
+		function decodeGeometry( draco, decoder, decoderBuffer, taskConfig ) {
 
-						if ( geometry.index ) buffers.push( geometry.index.array.buffer );
+			var attributeIDs = taskConfig.attributeIDs;
+			var attributeTypes = taskConfig.attributeTypes;
+			var dracoGeometry;
+			var decodingStatus;
+			var geometryType = decoder.GetEncodedGeometryType( decoderBuffer );
 
-						self.postMessage( { type: 'decode', id: message.id, geometry }, buffers );
+			if ( geometryType === draco.TRIANGULAR_MESH ) {
 
-					} catch ( error ) {
+				dracoGeometry = new draco.Mesh();
+				decodingStatus = decoder.DecodeBufferToMesh( decoderBuffer, dracoGeometry );
 
-						console.error( error );
+			} else if ( geometryType === draco.POINT_CLOUD ) {
 
-						self.postMessage( { type: 'error', id: message.id, error: error.message } );
+				dracoGeometry = new draco.PointCloud();
+				decodingStatus = decoder.DecodeBufferToPointCloud( decoderBuffer, dracoGeometry );
 
-					} finally {
+			} else {
 
-						draco.destroy( decoderBuffer );
-						draco.destroy( decoder );
+				throw new Error( 'THREE.DRACOLoader: Unexpected geometry type.' );
 
-					}
+			}
 
-				} );
-				break;
+			if ( ! decodingStatus.ok() || dracoGeometry.ptr === 0 ) {
 
-		}
+				throw new Error( 'THREE.DRACOLoader: Decoding failed: ' + decodingStatus.error_msg() );
 
-	};
+			}
 
-	function decodeGeometry( draco, decoder, decoderBuffer, taskConfig ) {
+			var geometry = {
+				index: null,
+				attributes: []
+			}; // Gather all vertex attributes.
 
-		var attributeIDs = taskConfig.attributeIDs;
-		var attributeTypes = taskConfig.attributeTypes;
+			for ( var attributeName in attributeIDs ) {
 
-		var dracoGeometry;
-		var decodingStatus;
+				var attributeType = self[ attributeTypes[ attributeName ] ];
+				var attribute;
+				var attributeID; // A Draco file may be created with default vertex attributes, whose attribute IDs
+				// are mapped 1:1 from their semantic name (POSITION, NORMAL, ...). Alternatively,
+				// a Draco file may contain a custom set of attributes, identified by known unique
+				// IDs. glTF files always do the latter, and `.drc` files typically do the former.
 
-		var geometryType = decoder.GetEncodedGeometryType( decoderBuffer );
+				if ( taskConfig.useUniqueIDs ) {
 
-		if ( geometryType === draco.TRIANGULAR_MESH ) {
+					attributeID = attributeIDs[ attributeName ];
+					attribute = decoder.GetAttributeByUniqueId( dracoGeometry, attributeID );
 
-			dracoGeometry = new draco.Mesh();
-			decodingStatus = decoder.DecodeBufferToMesh( decoderBuffer, dracoGeometry );
+				} else {
 
-		} else if ( geometryType === draco.POINT_CLOUD ) {
+					attributeID = decoder.GetAttributeId( dracoGeometry, draco[ attributeIDs[ attributeName ] ] );
+					if ( attributeID === - 1 ) continue;
+					attribute = decoder.GetAttribute( dracoGeometry, attributeID );
 
-			dracoGeometry = new draco.PointCloud();
-			decodingStatus = decoder.DecodeBufferToPointCloud( decoderBuffer, dracoGeometry );
+				}
 
-		} else {
+				geometry.attributes.push( decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) );
 
-			throw new Error( 'THREE.DRACOLoader: Unexpected geometry type.' );
+			} // Add index.
 
-		}
 
-		if ( ! decodingStatus.ok() || dracoGeometry.ptr === 0 ) {
+			if ( geometryType === draco.TRIANGULAR_MESH ) {
 
-			throw new Error( 'THREE.DRACOLoader: Decoding failed: ' + decodingStatus.error_msg() );
+				geometry.index = decodeIndex( draco, decoder, dracoGeometry );
 
-		}
+			}
 
-		var geometry = { index: null, attributes: [] };
+			draco.destroy( dracoGeometry );
+			return geometry;
 
-		// Gather all vertex attributes.
-		for ( var attributeName in attributeIDs ) {
+		}
 
-			var attributeType = self[ attributeTypes[ attributeName ] ];
+		function decodeIndex( draco, decoder, dracoGeometry ) {
 
-			var attribute;
-			var attributeID;
+			var numFaces = dracoGeometry.num_faces();
+			var numIndices = numFaces * 3;
+			var byteLength = numIndices * 4;
 
-			// A Draco file may be created with default vertex attributes, whose attribute IDs
-			// are mapped 1:1 from their semantic name (POSITION, NORMAL, ...). Alternatively,
-			// a Draco file may contain a custom set of attributes, identified by known unique
-			// IDs. glTF files always do the latter, and `.drc` files typically do the former.
-			if ( taskConfig.useUniqueIDs ) {
+			var ptr = draco._malloc( byteLength );
 
-				attributeID = attributeIDs[ attributeName ];
-				attribute = decoder.GetAttributeByUniqueId( dracoGeometry, attributeID );
+			decoder.GetTrianglesUInt32Array( dracoGeometry, byteLength, ptr );
+			var index = new Uint32Array( draco.HEAPF32.buffer, ptr, numIndices ).slice();
 
-			} else {
+			draco._free( ptr );
 
-				attributeID = decoder.GetAttributeId( dracoGeometry, draco[ attributeIDs[ attributeName ] ] );
+			return {
+				array: index,
+				itemSize: 1
+			};
 
-				if ( attributeID === - 1 ) continue;
+		}
 
-				attribute = decoder.GetAttribute( dracoGeometry, attributeID );
+		function decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) {
 
-			}
+			var numComponents = attribute.num_components();
+			var numPoints = dracoGeometry.num_points();
+			var numValues = numPoints * numComponents;
+			var byteLength = numValues * attributeType.BYTES_PER_ELEMENT;
+			var dataType = getDracoDataType( draco, attributeType );
 
-			geometry.attributes.push( decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) );
+			var ptr = draco._malloc( byteLength );
 
-		}
+			decoder.GetAttributeDataArrayForAllPoints( dracoGeometry, attribute, dataType, byteLength, ptr );
+			var array = new attributeType( draco.HEAPF32.buffer, ptr, numValues ).slice();
 
-		// Add index.
-		if ( geometryType === draco.TRIANGULAR_MESH ) {
+			draco._free( ptr );
 
-			geometry.index = decodeIndex( draco, decoder, dracoGeometry );
+			return {
+				name: attributeName,
+				array: array,
+				itemSize: numComponents
+			};
 
 		}
 
-		draco.destroy( dracoGeometry );
-
-		return geometry;
-
-	}
+		function getDracoDataType( draco, attributeType ) {
 
-	function decodeIndex( draco, decoder, dracoGeometry ) {
+			switch ( attributeType ) {
 
-		var numFaces = dracoGeometry.num_faces();
-		var numIndices = numFaces * 3;
-		var byteLength = numIndices * 4;
+				case Float32Array:
+					return draco.DT_FLOAT32;
 
-		var ptr = draco._malloc( byteLength );
-		decoder.GetTrianglesUInt32Array( dracoGeometry, byteLength, ptr );
-		var index = new Uint32Array( draco.HEAPF32.buffer, ptr, numIndices ).slice();
-		draco._free( ptr );
+				case Int8Array:
+					return draco.DT_INT8;
 
-		return { array: index, itemSize: 1 };
+				case Int16Array:
+					return draco.DT_INT16;
 
-	}
+				case Int32Array:
+					return draco.DT_INT32;
 
-	function decodeAttribute( draco, decoder, dracoGeometry, attributeName, attributeType, attribute ) {
+				case Uint8Array:
+					return draco.DT_UINT8;
 
-		var numComponents = attribute.num_components();
-		var numPoints = dracoGeometry.num_points();
-		var numValues = numPoints * numComponents;
-		var byteLength = numValues * attributeType.BYTES_PER_ELEMENT;
-		var dataType = getDracoDataType( draco, attributeType );
+				case Uint16Array:
+					return draco.DT_UINT16;
 
-		var ptr = draco._malloc( byteLength );
-		decoder.GetAttributeDataArrayForAllPoints( dracoGeometry, attribute, dataType, byteLength, ptr );
-		var array = new attributeType( draco.HEAPF32.buffer, ptr, numValues ).slice();
-		draco._free( ptr );
+				case Uint32Array:
+					return draco.DT_UINT32;
 
-		return {
-			name: attributeName,
-			array: array,
-			itemSize: numComponents
-		};
+			}
 
-	}
+		}
 
-	function getDracoDataType( draco, attributeType ) {
+	};
 
-		switch ( attributeType ) {
+	DRACOLoader.taskCache = new WeakMap();
+	/** Deprecated static methods */
 
-			case Float32Array: return draco.DT_FLOAT32;
-			case Int8Array: return draco.DT_INT8;
-			case Int16Array: return draco.DT_INT16;
-			case Int32Array: return draco.DT_INT32;
-			case Uint8Array: return draco.DT_UINT8;
-			case Uint16Array: return draco.DT_UINT16;
-			case Uint32Array: return draco.DT_UINT32;
+	/** @deprecated */
 
-		}
+	DRACOLoader.setDecoderPath = function () {
 
-	}
+		console.warn( 'THREE.DRACOLoader: The .setDecoderPath() method has been removed. Use instance methods.' );
 
-};
+	};
+	/** @deprecated */
 
-THREE.DRACOLoader.taskCache = new WeakMap();
 
-/** Deprecated static methods */
+	DRACOLoader.setDecoderConfig = function () {
 
-/** @deprecated */
-THREE.DRACOLoader.setDecoderPath = function () {
+		console.warn( 'THREE.DRACOLoader: The .setDecoderConfig() method has been removed. Use instance methods.' );
 
-	console.warn( 'THREE.DRACOLoader: The .setDecoderPath() method has been removed. Use instance methods.' );
+	};
+	/** @deprecated */
 
-};
 
-/** @deprecated */
-THREE.DRACOLoader.setDecoderConfig = function () {
+	DRACOLoader.releaseDecoderModule = function () {
 
-	console.warn( 'THREE.DRACOLoader: The .setDecoderConfig() method has been removed. Use instance methods.' );
+		console.warn( 'THREE.DRACOLoader: The .releaseDecoderModule() method has been removed. Use instance methods.' );
 
-};
+	};
+	/** @deprecated */
 
-/** @deprecated */
-THREE.DRACOLoader.releaseDecoderModule = function () {
 
-	console.warn( 'THREE.DRACOLoader: The .releaseDecoderModule() method has been removed. Use instance methods.' );
+	DRACOLoader.getDecoderModule = function () {
 
-};
+		console.warn( 'THREE.DRACOLoader: The .getDecoderModule() method has been removed. Use instance methods.' );
 
-/** @deprecated */
-THREE.DRACOLoader.getDecoderModule = function () {
+	};
 
-	console.warn( 'THREE.DRACOLoader: The .getDecoderModule() method has been removed. Use instance methods.' );
+	THREE.DRACOLoader = DRACOLoader;
 
-};
+} )();

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 430 - 474
examples/js/loaders/EXRLoader.js


+ 2385 - 2771
examples/js/loaders/FBXLoader.js

@@ -1,10 +1,12 @@
-/**
- * Loader loads FBX file and generates Group representing FBX scene.
+( function () {
+
+	/**
+ * THREE.Loader loads FBX file and generates THREE.Group representing FBX scene.
  * Requires FBX file to be >= 7.0 and in ASCII or >= 6400 in Binary format
  * Versions lower than this may load but will probably have errors
  *
  * Needs Support:
- *  Morph normals / blend shape normals
+ *	Morph normals / blend shape normals
  *
  * FBX format references:
  * 	https://wiki.blender.org/index.php/User:Mont29/Foundation/FBX_File_Structure
@@ -14,204 +16,189 @@
  *		https://code.blender.org/2013/08/fbx-binary-file-format-specification/
  */
 
+	var FBXLoader = function () {
 
-THREE.FBXLoader = ( function () {
-
-	var fbxTree;
-	var connections;
-	var sceneGraph;
-
-	function FBXLoader( manager ) {
-
-		THREE.Loader.call( this, manager );
+		var fbxTree;
+		var connections;
+		var sceneGraph;
 
-	}
+		function FBXLoader( manager ) {
 
-	FBXLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+			THREE.Loader.call( this, manager );
 
-		constructor: FBXLoader,
-
-		load: function ( url, onLoad, onProgress, onError ) {
+		}
 
-			var scope = this;
+		FBXLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+			constructor: FBXLoader,
+			load: function ( url, onLoad, onProgress, onError ) {
 
-			var path = ( scope.path === '' ) ? THREE.LoaderUtils.extractUrlBase( url ) : scope.path;
+				var scope = this;
+				var path = scope.path === '' ? THREE.LoaderUtils.extractUrlBase( url ) : scope.path;
+				var loader = new THREE.FileLoader( this.manager );
+				loader.setPath( scope.path );
+				loader.setResponseType( 'arraybuffer' );
+				loader.setRequestHeader( scope.requestHeader );
+				loader.setWithCredentials( scope.withCredentials );
+				loader.load( url, function ( buffer ) {
 
-			var loader = new THREE.FileLoader( this.manager );
-			loader.setPath( scope.path );
-			loader.setResponseType( 'arraybuffer' );
-			loader.setRequestHeader( scope.requestHeader );
-			loader.setWithCredentials( scope.withCredentials );
+					try {
 
-			loader.load( url, function ( buffer ) {
+						onLoad( scope.parse( buffer, path ) );
 
-				try {
+					} catch ( e ) {
 
-					onLoad( scope.parse( buffer, path ) );
+						if ( onError ) {
 
-				} catch ( e ) {
+							onError( e );
 
-					if ( onError ) {
+						} else {
 
-						onError( e );
+							console.error( e );
 
-					} else {
+						}
 
-						console.error( e );
+						scope.manager.itemError( url );
 
 					}
 
-					scope.manager.itemError( url );
+				}, onProgress, onError );
 
-				}
+			},
+			parse: function ( FBXBuffer, path ) {
 
-			}, onProgress, onError );
+				if ( isFbxFormatBinary( FBXBuffer ) ) {
 
-		},
+					fbxTree = new BinaryParser().parse( FBXBuffer );
 
-		parse: function ( FBXBuffer, path ) {
+				} else {
 
-			if ( isFbxFormatBinary( FBXBuffer ) ) {
+					var FBXText = convertArrayBufferToString( FBXBuffer );
 
-				fbxTree = new BinaryParser().parse( FBXBuffer );
+					if ( ! isFbxFormatASCII( FBXText ) ) {
 
-			} else {
+						throw new Error( 'THREE.FBXLoader: Unknown format.' );
 
-				var FBXText = convertArrayBufferToString( FBXBuffer );
+					}
 
-				if ( ! isFbxFormatASCII( FBXText ) ) {
+					if ( getFbxVersion( FBXText ) < 7000 ) {
 
-					throw new Error( 'THREE.FBXLoader: Unknown format.' );
+						throw new Error( 'THREE.FBXLoader: FBX version not supported, FileVersion: ' + getFbxVersion( FBXText ) );
 
-				}
+					}
 
-				if ( getFbxVersion( FBXText ) < 7000 ) {
+					fbxTree = new TextParser().parse( FBXText );
 
-					throw new Error( 'THREE.FBXLoader: FBX version not supported, FileVersion: ' + getFbxVersion( FBXText ) );
+				} // console.log( fbxTree );
 
-				}
 
-				fbxTree = new TextParser().parse( FBXText );
+				var textureLoader = new THREE.TextureLoader( this.manager ).setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin );
+				return new FBXTreeParser( textureLoader, this.manager ).parse( fbxTree );
 
 			}
+		} ); // Parse the FBXTree object returned by the BinaryParser or TextParser and return a THREE.Group
 
-			// console.log( fbxTree );
+		function FBXTreeParser( textureLoader, manager ) {
 
-			var textureLoader = new THREE.TextureLoader( this.manager ).setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin );
-
-			return new FBXTreeParser( textureLoader, this.manager ).parse( fbxTree );
+			this.textureLoader = textureLoader;
+			this.manager = manager;
 
 		}
 
-	} );
-
-	// Parse the FBXTree object returned by the BinaryParser or TextParser and return a THREE.Group
-	function FBXTreeParser( textureLoader, manager ) {
-
-		this.textureLoader = textureLoader;
-		this.manager = manager;
-
-	}
-
-	FBXTreeParser.prototype = {
-
-		constructor: FBXTreeParser,
-
-		parse: function () {
-
-			connections = this.parseConnections();
+		FBXTreeParser.prototype = {
+			constructor: FBXTreeParser,
+			parse: function () {
 
-			var images = this.parseImages();
-			var textures = this.parseTextures( images );
-			var materials = this.parseMaterials( textures );
-			var deformers = this.parseDeformers();
-			var geometryMap = new GeometryParser().parse( deformers );
+				connections = this.parseConnections();
+				var images = this.parseImages();
+				var textures = this.parseTextures( images );
+				var materials = this.parseMaterials( textures );
+				var deformers = this.parseDeformers();
+				var geometryMap = new GeometryParser().parse( deformers );
+				this.parseScene( deformers, geometryMap, materials );
+				return sceneGraph;
 
-			this.parseScene( deformers, geometryMap, materials );
+			},
+			// Parses FBXTree.Connections which holds parent-child connections between objects (e.g. material -> texture, model->geometry )
+			// and details the connection type
+			parseConnections: function () {
 
-			return sceneGraph;
+				var connectionMap = new Map();
 
-		},
+				if ( 'Connections' in fbxTree ) {
 
-		// Parses FBXTree.Connections which holds parent-child connections between objects (e.g. material -> texture, model->geometry )
-		// and details the connection type
-		parseConnections: function () {
-
-			var connectionMap = new Map();
-
-			if ( 'Connections' in fbxTree ) {
-
-				var rawConnections = fbxTree.Connections.connections;
-
-				rawConnections.forEach( function ( rawConnection ) {
-
-					var fromID = rawConnection[ 0 ];
-					var toID = rawConnection[ 1 ];
-					var relationship = rawConnection[ 2 ];
-
-					if ( ! connectionMap.has( fromID ) ) {
-
-						connectionMap.set( fromID, {
-							parents: [],
-							children: []
-						} );
+					var rawConnections = fbxTree.Connections.connections;
+					rawConnections.forEach( function ( rawConnection ) {
 
-					}
+						var fromID = rawConnection[ 0 ];
+						var toID = rawConnection[ 1 ];
+						var relationship = rawConnection[ 2 ];
 
-					var parentRelationship = { ID: toID, relationship: relationship };
-					connectionMap.get( fromID ).parents.push( parentRelationship );
+						if ( ! connectionMap.has( fromID ) ) {
 
-					if ( ! connectionMap.has( toID ) ) {
+							connectionMap.set( fromID, {
+								parents: [],
+								children: []
+							} );
 
-						connectionMap.set( toID, {
-							parents: [],
-							children: []
-						} );
+						}
 
-					}
+						var parentRelationship = {
+							ID: toID,
+							relationship: relationship
+						};
+						connectionMap.get( fromID ).parents.push( parentRelationship );
 
-					var childRelationship = { ID: fromID, relationship: relationship };
-					connectionMap.get( toID ).children.push( childRelationship );
+						if ( ! connectionMap.has( toID ) ) {
 
-				} );
+							connectionMap.set( toID, {
+								parents: [],
+								children: []
+							} );
 
-			}
+						}
 
-			return connectionMap;
+						var childRelationship = {
+							ID: fromID,
+							relationship: relationship
+						};
+						connectionMap.get( toID ).children.push( childRelationship );
 
-		},
+					} );
 
-		// Parse FBXTree.Objects.Video for embedded image data
-		// These images are connected to textures in FBXTree.Objects.Textures
-		// via FBXTree.Connections.
-		parseImages: function () {
+				}
 
-			var images = {};
-			var blobs = {};
+				return connectionMap;
 
-			if ( 'Video' in fbxTree.Objects ) {
+			},
+			// Parse FBXTree.Objects.Video for embedded image data
+			// These images are connected to textures in FBXTree.Objects.Textures
+			// via FBXTree.Connections.
+			parseImages: function () {
 
-				var videoNodes = fbxTree.Objects.Video;
+				var images = {};
+				var blobs = {};
 
-				for ( var nodeID in videoNodes ) {
+				if ( 'Video' in fbxTree.Objects ) {
 
-					var videoNode = videoNodes[ nodeID ];
+					var videoNodes = fbxTree.Objects.Video;
 
-					var id = parseInt( nodeID );
+					for ( var nodeID in videoNodes ) {
 
-					images[ id ] = videoNode.RelativeFilename || videoNode.Filename;
+						var videoNode = videoNodes[ nodeID ];
+						var id = parseInt( nodeID );
+						images[ id ] = videoNode.RelativeFilename || videoNode.Filename; // raw image data is in videoNode.Content
 
-					// raw image data is in videoNode.Content
-					if ( 'Content' in videoNode ) {
+						if ( 'Content' in videoNode ) {
 
-						var arrayBufferContent = ( videoNode.Content instanceof ArrayBuffer ) && ( videoNode.Content.byteLength > 0 );
-						var base64Content = ( typeof videoNode.Content === 'string' ) && ( videoNode.Content !== '' );
+							var arrayBufferContent = videoNode.Content instanceof ArrayBuffer && videoNode.Content.byteLength > 0;
+							var base64Content = typeof videoNode.Content === 'string' && videoNode.Content !== '';
 
-						if ( arrayBufferContent || base64Content ) {
+							if ( arrayBufferContent || base64Content ) {
 
-							var image = this.parseImage( videoNodes[ nodeID ] );
+								var image = this.parseImage( videoNodes[ nodeID ] );
+								blobs[ videoNode.RelativeFilename || videoNode.Filename ] = image;
 
-							blobs[ videoNode.RelativeFilename || videoNode.Filename ] = image;
+							}
 
 						}
 
@@ -219,3956 +206,3583 @@ THREE.FBXLoader = ( function () {
 
 				}
 
-			}
-
-			for ( var id in images ) {
-
-				var filename = images[ id ];
-
-				if ( blobs[ filename ] !== undefined ) images[ id ] = blobs[ filename ];
-				else images[ id ] = images[ id ].split( '\\' ).pop();
-
-			}
-
-			return images;
-
-		},
-
-		// Parse embedded image data in FBXTree.Video.Content
-		parseImage: function ( videoNode ) {
+				for ( var id in images ) {
 
-			var content = videoNode.Content;
-			var fileName = videoNode.RelativeFilename || videoNode.Filename;
-			var extension = fileName.slice( fileName.lastIndexOf( '.' ) + 1 ).toLowerCase();
+					var filename = images[ id ];
+					if ( blobs[ filename ] !== undefined ) images[ id ] = blobs[ filename ]; else images[ id ] = images[ id ].split( '\\' ).pop();
 
-			var type;
-
-			switch ( extension ) {
-
-				case 'bmp':
+				}
 
-					type = 'image/bmp';
-					break;
+				return images;
 
-				case 'jpg':
-				case 'jpeg':
+			},
+			// Parse embedded image data in FBXTree.Video.Content
+			parseImage: function ( videoNode ) {
 
-					type = 'image/jpeg';
-					break;
+				var content = videoNode.Content;
+				var fileName = videoNode.RelativeFilename || videoNode.Filename;
+				var extension = fileName.slice( fileName.lastIndexOf( '.' ) + 1 ).toLowerCase();
+				var type;
 
-				case 'png':
+				switch ( extension ) {
 
-					type = 'image/png';
-					break;
+					case 'bmp':
+						type = 'image/bmp';
+						break;
 
-				case 'tif':
+					case 'jpg':
+					case 'jpeg':
+						type = 'image/jpeg';
+						break;
 
-					type = 'image/tiff';
-					break;
+					case 'png':
+						type = 'image/png';
+						break;
 
-				case 'tga':
+					case 'tif':
+						type = 'image/tiff';
+						break;
 
-					if ( this.manager.getHandler( '.tga' ) === null ) {
+					case 'tga':
+						if ( this.manager.getHandler( '.tga' ) === null ) {
 
-						console.warn( 'FBXLoader: TGA loader not found, skipping ', fileName );
+							console.warn( 'FBXLoader: TGA loader not found, skipping ', fileName );
 
-					}
+						}
 
-					type = 'image/tga';
-					break;
+						type = 'image/tga';
+						break;
 
-				default:
+					default:
+						console.warn( 'FBXLoader: Image type "' + extension + '" is not supported.' );
+						return;
 
-					console.warn( 'FBXLoader: Image type "' + extension + '" is not supported.' );
-					return;
+				}
 
-			}
+				if ( typeof content === 'string' ) {
 
-			if ( typeof content === 'string' ) { // ASCII format
+					// ASCII format
+					return 'data:' + type + ';base64,' + content;
 
-				return 'data:' + type + ';base64,' + content;
+				} else {
 
-			} else { // Binary Format
+					// Binary Format
+					var array = new Uint8Array( content );
+					return window.URL.createObjectURL( new Blob( [ array ], {
+						type: type
+					} ) );
 
-				var array = new Uint8Array( content );
-				return window.URL.createObjectURL( new Blob( [ array ], { type: type } ) );
+				}
 
-			}
+			},
+			// Parse nodes in FBXTree.Objects.Texture
+			// These contain details such as UV scaling, cropping, rotation etc and are connected
+			// to images in FBXTree.Objects.Video
+			parseTextures: function ( images ) {
 
-		},
+				var textureMap = new Map();
 
-		// Parse nodes in FBXTree.Objects.Texture
-		// These contain details such as UV scaling, cropping, rotation etc and are connected
-		// to images in FBXTree.Objects.Video
-		parseTextures: function ( images ) {
+				if ( 'Texture' in fbxTree.Objects ) {
 
-			var textureMap = new Map();
+					var textureNodes = fbxTree.Objects.Texture;
 
-			if ( 'Texture' in fbxTree.Objects ) {
+					for ( var nodeID in textureNodes ) {
 
-				var textureNodes = fbxTree.Objects.Texture;
-				for ( var nodeID in textureNodes ) {
+						var texture = this.parseTexture( textureNodes[ nodeID ], images );
+						textureMap.set( parseInt( nodeID ), texture );
 
-					var texture = this.parseTexture( textureNodes[ nodeID ], images );
-					textureMap.set( parseInt( nodeID ), texture );
+					}
 
 				}
 
-			}
-
-			return textureMap;
-
-		},
-
-		// Parse individual node in FBXTree.Objects.Texture
-		parseTexture: function ( textureNode, images ) {
-
-			var texture = this.loadTexture( textureNode, images );
-
-			texture.ID = textureNode.id;
-
-			texture.name = textureNode.attrName;
+				return textureMap;
 
-			var wrapModeU = textureNode.WrapModeU;
-			var wrapModeV = textureNode.WrapModeV;
+			},
+			// Parse individual node in FBXTree.Objects.Texture
+			parseTexture: function ( textureNode, images ) {
 
-			var valueU = wrapModeU !== undefined ? wrapModeU.value : 0;
-			var valueV = wrapModeV !== undefined ? wrapModeV.value : 0;
+				var texture = this.loadTexture( textureNode, images );
+				texture.ID = textureNode.id;
+				texture.name = textureNode.attrName;
+				var wrapModeU = textureNode.WrapModeU;
+				var wrapModeV = textureNode.WrapModeV;
+				var valueU = wrapModeU !== undefined ? wrapModeU.value : 0;
+				var valueV = wrapModeV !== undefined ? wrapModeV.value : 0; // http://download.autodesk.com/us/fbx/SDKdocs/FBX_SDK_Help/files/fbxsdkref/class_k_fbx_texture.html#889640e63e2e681259ea81061b85143a
+				// 0: repeat(default), 1: clamp
 
-			// http://download.autodesk.com/us/fbx/SDKdocs/FBX_SDK_Help/files/fbxsdkref/class_k_fbx_texture.html#889640e63e2e681259ea81061b85143a
-			// 0: repeat(default), 1: clamp
+				texture.wrapS = valueU === 0 ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping;
+				texture.wrapT = valueV === 0 ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping;
 
-			texture.wrapS = valueU === 0 ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping;
-			texture.wrapT = valueV === 0 ? THREE.RepeatWrapping : THREE.ClampToEdgeWrapping;
+				if ( 'Scaling' in textureNode ) {
 
-			if ( 'Scaling' in textureNode ) {
+					var values = textureNode.Scaling.value;
+					texture.repeat.x = values[ 0 ];
+					texture.repeat.y = values[ 1 ];
 
-				var values = textureNode.Scaling.value;
-
-				texture.repeat.x = values[ 0 ];
-				texture.repeat.y = values[ 1 ];
+				}
 
-			}
+				return texture;
 
-			return texture;
+			},
+			// load a texture specified as a blob or data URI, or via an external URL using THREE.TextureLoader
+			loadTexture: function ( textureNode, images ) {
 
-		},
+				var fileName;
+				var currentPath = this.textureLoader.path;
+				var children = connections.get( textureNode.id ).children;
 
-		// load a texture specified as a blob or data URI, or via an external URL using THREE.TextureLoader
-		loadTexture: function ( textureNode, images ) {
+				if ( children !== undefined && children.length > 0 && images[ children[ 0 ].ID ] !== undefined ) {
 
-			var fileName;
+					fileName = images[ children[ 0 ].ID ];
 
-			var currentPath = this.textureLoader.path;
+					if ( fileName.indexOf( 'blob:' ) === 0 || fileName.indexOf( 'data:' ) === 0 ) {
 
-			var children = connections.get( textureNode.id ).children;
+						this.textureLoader.setPath( undefined );
 
-			if ( children !== undefined && children.length > 0 && images[ children[ 0 ].ID ] !== undefined ) {
+					}
 
-				fileName = images[ children[ 0 ].ID ];
+				}
 
-				if ( fileName.indexOf( 'blob:' ) === 0 || fileName.indexOf( 'data:' ) === 0 ) {
+				var texture;
+				var extension = textureNode.FileName.slice( - 3 ).toLowerCase();
 
-					this.textureLoader.setPath( undefined );
+				if ( extension === 'tga' ) {
 
-				}
+					var loader = this.manager.getHandler( '.tga' );
 
-			}
+					if ( loader === null ) {
 
-			var texture;
+						console.warn( 'FBXLoader: TGA loader not found, creating placeholder texture for', textureNode.RelativeFilename );
+						texture = new THREE.Texture();
 
-			var extension = textureNode.FileName.slice( - 3 ).toLowerCase();
+					} else {
 
-			if ( extension === 'tga' ) {
+						texture = loader.load( fileName );
 
-				var loader = this.manager.getHandler( '.tga' );
+					}
 
-				if ( loader === null ) {
+				} else if ( extension === 'psd' ) {
 
-					console.warn( 'FBXLoader: TGA loader not found, creating placeholder texture for', textureNode.RelativeFilename );
+					console.warn( 'FBXLoader: PSD textures are not supported, creating placeholder texture for', textureNode.RelativeFilename );
 					texture = new THREE.Texture();
 
 				} else {
 
-					texture = loader.load( fileName );
+					texture = this.textureLoader.load( fileName );
 
 				}
 
-			} else if ( extension === 'psd' ) {
-
-				console.warn( 'FBXLoader: PSD textures are not supported, creating placeholder texture for', textureNode.RelativeFilename );
-				texture = new THREE.Texture();
-
-			} else {
-
-				texture = this.textureLoader.load( fileName );
-
-			}
-
-			this.textureLoader.setPath( currentPath );
+				this.textureLoader.setPath( currentPath );
+				return texture;
 
-			return texture;
+			},
+			// Parse nodes in FBXTree.Objects.Material
+			parseMaterials: function ( textureMap ) {
 
-		},
+				var materialMap = new Map();
 
-		// Parse nodes in FBXTree.Objects.Material
-		parseMaterials: function ( textureMap ) {
+				if ( 'Material' in fbxTree.Objects ) {
 
-			var materialMap = new Map();
+					var materialNodes = fbxTree.Objects.Material;
 
-			if ( 'Material' in fbxTree.Objects ) {
+					for ( var nodeID in materialNodes ) {
 
-				var materialNodes = fbxTree.Objects.Material;
+						var material = this.parseMaterial( materialNodes[ nodeID ], textureMap );
+						if ( material !== null ) materialMap.set( parseInt( nodeID ), material );
 
-				for ( var nodeID in materialNodes ) {
-
-					var material = this.parseMaterial( materialNodes[ nodeID ], textureMap );
-
-					if ( material !== null ) materialMap.set( parseInt( nodeID ), material );
+					}
 
 				}
 
-			}
-
-			return materialMap;
-
-		},
-
-		// Parse single node in FBXTree.Objects.Material
-		// Materials are connected to texture maps in FBXTree.Objects.Textures
-		// FBX format currently only supports Lambert and Phong shading models
-		parseMaterial: function ( materialNode, textureMap ) {
-
-			var ID = materialNode.id;
-			var name = materialNode.attrName;
-			var type = materialNode.ShadingModel;
-
-			// Case where FBX wraps shading model in property object.
-			if ( typeof type === 'object' ) {
-
-				type = type.value;
-
-			}
-
-			// Ignore unused materials which don't have any connections.
-			if ( ! connections.has( ID ) ) return null;
-
-			var parameters = this.parseParameters( materialNode, textureMap, ID );
-
-			var material;
-
-			switch ( type.toLowerCase() ) {
-
-				case 'phong':
-					material = new THREE.MeshPhongMaterial();
-					break;
-				case 'lambert':
-					material = new THREE.MeshLambertMaterial();
-					break;
-				default:
-					console.warn( 'THREE.FBXLoader: unknown material type "%s". Defaulting to MeshPhongMaterial.', type );
-					material = new THREE.MeshPhongMaterial();
-					break;
-
-			}
-
-			material.setValues( parameters );
-			material.name = name;
+				return materialMap;
 
-			return material;
+			},
+			// Parse single node in FBXTree.Objects.Material
+			// Materials are connected to texture maps in FBXTree.Objects.Textures
+			// FBX format currently only supports Lambert and Phong shading models
+			parseMaterial: function ( materialNode, textureMap ) {
 
-		},
+				var ID = materialNode.id;
+				var name = materialNode.attrName;
+				var type = materialNode.ShadingModel; // Case where FBX wraps shading model in property object.
 
-		// Parse FBX material and return parameters suitable for a three.js material
-		// Also parse the texture map and return any textures associated with the material
-		parseParameters: function ( materialNode, textureMap, ID ) {
+				if ( typeof type === 'object' ) {
 
-			var parameters = {};
+					type = type.value;
 
-			if ( materialNode.BumpFactor ) {
+				} // Ignore unused materials which don't have any connections.
 
-				parameters.bumpScale = materialNode.BumpFactor.value;
 
-			}
-
-			if ( materialNode.Diffuse ) {
-
-				parameters.color = new THREE.Color().fromArray( materialNode.Diffuse.value );
-
-			} else if ( materialNode.DiffuseColor && ( materialNode.DiffuseColor.type === 'Color' || materialNode.DiffuseColor.type === 'ColorRGB' ) ) {
-
-				// The blender exporter exports diffuse here instead of in materialNode.Diffuse
-				parameters.color = new THREE.Color().fromArray( materialNode.DiffuseColor.value );
-
-			}
-
-			if ( materialNode.DisplacementFactor ) {
+				if ( ! connections.has( ID ) ) return null;
+				var parameters = this.parseParameters( materialNode, textureMap, ID );
+				var material;
 
-				parameters.displacementScale = materialNode.DisplacementFactor.value;
+				switch ( type.toLowerCase() ) {
 
-			}
-
-			if ( materialNode.Emissive ) {
-
-				parameters.emissive = new THREE.Color().fromArray( materialNode.Emissive.value );
+					case 'phong':
+						material = new THREE.MeshPhongMaterial();
+						break;
 
-			} else if ( materialNode.EmissiveColor && ( materialNode.EmissiveColor.type === 'Color' || materialNode.EmissiveColor.type === 'ColorRGB' ) ) {
+					case 'lambert':
+						material = new THREE.MeshLambertMaterial();
+						break;
 
-				// The blender exporter exports emissive color here instead of in materialNode.Emissive
-				parameters.emissive = new THREE.Color().fromArray( materialNode.EmissiveColor.value );
+					default:
+						console.warn( 'THREE.FBXLoader: unknown material type "%s". Defaulting to THREE.MeshPhongMaterial.', type );
+						material = new THREE.MeshPhongMaterial();
+						break;
 
-			}
+				}
 
-			if ( materialNode.EmissiveFactor ) {
+				material.setValues( parameters );
+				material.name = name;
+				return material;
 
-				parameters.emissiveIntensity = parseFloat( materialNode.EmissiveFactor.value );
+			},
+			// Parse FBX material and return parameters suitable for a three.js material
+			// Also parse the texture map and return any textures associated with the material
+			parseParameters: function ( materialNode, textureMap, ID ) {
 
-			}
+				var parameters = {};
 
-			if ( materialNode.Opacity ) {
+				if ( materialNode.BumpFactor ) {
 
-				parameters.opacity = parseFloat( materialNode.Opacity.value );
+					parameters.bumpScale = materialNode.BumpFactor.value;
 
-			}
+				}
 
-			if ( parameters.opacity < 1.0 ) {
+				if ( materialNode.Diffuse ) {
 
-				parameters.transparent = true;
+					parameters.color = new THREE.Color().fromArray( materialNode.Diffuse.value );
 
-			}
+				} else if ( materialNode.DiffuseColor && ( materialNode.DiffuseColor.type === 'Color' || materialNode.DiffuseColor.type === 'ColorRGB' ) ) {
 
-			if ( materialNode.ReflectionFactor ) {
+					// The blender exporter exports diffuse here instead of in materialNode.Diffuse
+					parameters.color = new THREE.Color().fromArray( materialNode.DiffuseColor.value );
 
-				parameters.reflectivity = materialNode.ReflectionFactor.value;
+				}
 
-			}
+				if ( materialNode.DisplacementFactor ) {
 
-			if ( materialNode.Shininess ) {
+					parameters.displacementScale = materialNode.DisplacementFactor.value;
 
-				parameters.shininess = materialNode.Shininess.value;
+				}
 
-			}
+				if ( materialNode.Emissive ) {
 
-			if ( materialNode.Specular ) {
+					parameters.emissive = new THREE.Color().fromArray( materialNode.Emissive.value );
 
-				parameters.specular = new THREE.Color().fromArray( materialNode.Specular.value );
+				} else if ( materialNode.EmissiveColor && ( materialNode.EmissiveColor.type === 'Color' || materialNode.EmissiveColor.type === 'ColorRGB' ) ) {
 
-			} else if ( materialNode.SpecularColor && materialNode.SpecularColor.type === 'Color' ) {
+					// The blender exporter exports emissive color here instead of in materialNode.Emissive
+					parameters.emissive = new THREE.Color().fromArray( materialNode.EmissiveColor.value );
 
-				// The blender exporter exports specular color here instead of in materialNode.Specular
-				parameters.specular = new THREE.Color().fromArray( materialNode.SpecularColor.value );
+				}
 
-			}
+				if ( materialNode.EmissiveFactor ) {
 
-			var scope = this;
-			connections.get( ID ).children.forEach( function ( child ) {
+					parameters.emissiveIntensity = parseFloat( materialNode.EmissiveFactor.value );
 
-				var type = child.relationship;
+				}
 
-				switch ( type ) {
+				if ( materialNode.Opacity ) {
 
-					case 'Bump':
-						parameters.bumpMap = scope.getTexture( textureMap, child.ID );
-						break;
+					parameters.opacity = parseFloat( materialNode.Opacity.value );
 
-					case 'Maya|TEX_ao_map':
-						parameters.aoMap = scope.getTexture( textureMap, child.ID );
-						break;
+				}
 
-					case 'DiffuseColor':
-					case 'Maya|TEX_color_map':
-						parameters.map = scope.getTexture( textureMap, child.ID );
-						parameters.map.encoding = THREE.sRGBEncoding;
-						break;
+				if ( parameters.opacity < 1.0 ) {
 
-					case 'DisplacementColor':
-						parameters.displacementMap = scope.getTexture( textureMap, child.ID );
-						break;
+					parameters.transparent = true;
 
-					case 'EmissiveColor':
-						parameters.emissiveMap = scope.getTexture( textureMap, child.ID );
-						parameters.emissiveMap.encoding = THREE.sRGBEncoding;
-						break;
+				}
 
-					case 'NormalMap':
-					case 'Maya|TEX_normal_map':
-						parameters.normalMap = scope.getTexture( textureMap, child.ID );
-						break;
+				if ( materialNode.ReflectionFactor ) {
 
-					case 'ReflectionColor':
-						parameters.envMap = scope.getTexture( textureMap, child.ID );
-						parameters.envMap.mapping = THREE.EquirectangularReflectionMapping;
-						parameters.envMap.encoding = THREE.sRGBEncoding;
-						break;
+					parameters.reflectivity = materialNode.ReflectionFactor.value;
 
-					case 'SpecularColor':
-						parameters.specularMap = scope.getTexture( textureMap, child.ID );
-						parameters.specularMap.encoding = THREE.sRGBEncoding;
-						break;
+				}
 
-					case 'TransparentColor':
-					case 'TransparencyFactor':
-						parameters.alphaMap = scope.getTexture( textureMap, child.ID );
-						parameters.transparent = true;
-						break;
+				if ( materialNode.Shininess ) {
 
-					case 'AmbientColor':
-					case 'ShininessExponent': // AKA glossiness map
-					case 'SpecularFactor': // AKA specularLevel
-					case 'VectorDisplacementColor': // NOTE: Seems to be a copy of DisplacementColor
-					default:
-						console.warn( 'THREE.FBXLoader: %s map is not supported in three.js, skipping texture.', type );
-						break;
+					parameters.shininess = materialNode.Shininess.value;
 
 				}
 
-			} );
+				if ( materialNode.Specular ) {
 
-			return parameters;
+					parameters.specular = new THREE.Color().fromArray( materialNode.Specular.value );
 
-		},
+				} else if ( materialNode.SpecularColor && materialNode.SpecularColor.type === 'Color' ) {
 
-		// get a texture from the textureMap for use by a material.
-		getTexture: function ( textureMap, id ) {
+					// The blender exporter exports specular color here instead of in materialNode.Specular
+					parameters.specular = new THREE.Color().fromArray( materialNode.SpecularColor.value );
 
-			// if the texture is a layered texture, just use the first layer and issue a warning
-			if ( 'LayeredTexture' in fbxTree.Objects && id in fbxTree.Objects.LayeredTexture ) {
+				}
 
-				console.warn( 'THREE.FBXLoader: layered textures are not supported in three.js. Discarding all but first layer.' );
-				id = connections.get( id ).children[ 0 ].ID;
+				var scope = this;
+				connections.get( ID ).children.forEach( function ( child ) {
 
-			}
+					var type = child.relationship;
 
-			return textureMap.get( id );
+					switch ( type ) {
 
-		},
+						case 'Bump':
+							parameters.bumpMap = scope.getTexture( textureMap, child.ID );
+							break;
 
-		// Parse nodes in FBXTree.Objects.Deformer
-		// Deformer node can contain skinning or Vertex Cache animation data, however only skinning is supported here
-		// Generates map of Skeleton-like objects for use later when generating and binding skeletons.
-		parseDeformers: function () {
+						case 'Maya|TEX_ao_map':
+							parameters.aoMap = scope.getTexture( textureMap, child.ID );
+							break;
 
-			var skeletons = {};
-			var morphTargets = {};
+						case 'DiffuseColor':
+						case 'Maya|TEX_color_map':
+							parameters.map = scope.getTexture( textureMap, child.ID );
+							parameters.map.encoding = THREE.sRGBEncoding;
+							break;
 
-			if ( 'Deformer' in fbxTree.Objects ) {
+						case 'DisplacementColor':
+							parameters.displacementMap = scope.getTexture( textureMap, child.ID );
+							break;
 
-				var DeformerNodes = fbxTree.Objects.Deformer;
+						case 'EmissiveColor':
+							parameters.emissiveMap = scope.getTexture( textureMap, child.ID );
+							parameters.emissiveMap.encoding = THREE.sRGBEncoding;
+							break;
 
-				for ( var nodeID in DeformerNodes ) {
+						case 'NormalMap':
+						case 'Maya|TEX_normal_map':
+							parameters.normalMap = scope.getTexture( textureMap, child.ID );
+							break;
 
-					var deformerNode = DeformerNodes[ nodeID ];
+						case 'ReflectionColor':
+							parameters.envMap = scope.getTexture( textureMap, child.ID );
+							parameters.envMap.mapping = THREE.EquirectangularReflectionMapping;
+							parameters.envMap.encoding = THREE.sRGBEncoding;
+							break;
 
-					var relationships = connections.get( parseInt( nodeID ) );
+						case 'SpecularColor':
+							parameters.specularMap = scope.getTexture( textureMap, child.ID );
+							parameters.specularMap.encoding = THREE.sRGBEncoding;
+							break;
 
-					if ( deformerNode.attrType === 'Skin' ) {
+						case 'TransparentColor':
+						case 'TransparencyFactor':
+							parameters.alphaMap = scope.getTexture( textureMap, child.ID );
+							parameters.transparent = true;
+							break;
 
-						var skeleton = this.parseSkeleton( relationships, DeformerNodes );
-						skeleton.ID = nodeID;
+						case 'AmbientColor':
+						case 'ShininessExponent': // AKA glossiness map
 
-						if ( relationships.parents.length > 1 ) console.warn( 'THREE.FBXLoader: skeleton attached to more than one geometry is not supported.' );
-						skeleton.geometryID = relationships.parents[ 0 ].ID;
+						case 'SpecularFactor': // AKA specularLevel
 
-						skeletons[ nodeID ] = skeleton;
+						case 'VectorDisplacementColor': // NOTE: Seems to be a copy of DisplacementColor
 
-					} else if ( deformerNode.attrType === 'BlendShape' ) {
+						default:
+							console.warn( 'THREE.FBXLoader: %s map is not supported in three.js, skipping texture.', type );
+							break;
 
-						var morphTarget = {
-							id: nodeID,
-						};
+					}
 
-						morphTarget.rawTargets = this.parseMorphTargets( relationships, DeformerNodes );
-						morphTarget.id = nodeID;
+				} );
+				return parameters;
 
-						if ( relationships.parents.length > 1 ) console.warn( 'THREE.FBXLoader: morph target attached to more than one geometry is not supported.' );
+			},
+			// get a texture from the textureMap for use by a material.
+			getTexture: function ( textureMap, id ) {
 
-						morphTargets[ nodeID ] = morphTarget;
+				// if the texture is a layered texture, just use the first layer and issue a warning
+				if ( 'LayeredTexture' in fbxTree.Objects && id in fbxTree.Objects.LayeredTexture ) {
 
-					}
+					console.warn( 'THREE.FBXLoader: layered textures are not supported in three.js. Discarding all but first layer.' );
+					id = connections.get( id ).children[ 0 ].ID;
 
 				}
 
-			}
-
-			return {
+				return textureMap.get( id );
 
-				skeletons: skeletons,
-				morphTargets: morphTargets,
+			},
+			// Parse nodes in FBXTree.Objects.Deformer
+			// Deformer node can contain skinning or Vertex Cache animation data, however only skinning is supported here
+			// Generates map of THREE.Skeleton-like objects for use later when generating and binding skeletons.
+			parseDeformers: function () {
 
-			};
+				var skeletons = {};
+				var morphTargets = {};
 
-		},
+				if ( 'Deformer' in fbxTree.Objects ) {
 
-		// Parse single nodes in FBXTree.Objects.Deformer
-		// The top level skeleton node has type 'Skin' and sub nodes have type 'Cluster'
-		// Each skin node represents a skeleton and each cluster node represents a bone
-		parseSkeleton: function ( relationships, deformerNodes ) {
+					var DeformerNodes = fbxTree.Objects.Deformer;
 
-			var rawBones = [];
+					for ( var nodeID in DeformerNodes ) {
 
-			relationships.children.forEach( function ( child ) {
+						var deformerNode = DeformerNodes[ nodeID ];
+						var relationships = connections.get( parseInt( nodeID ) );
 
-				var boneNode = deformerNodes[ child.ID ];
+						if ( deformerNode.attrType === 'Skin' ) {
 
-				if ( boneNode.attrType !== 'Cluster' ) return;
+							var skeleton = this.parseSkeleton( relationships, DeformerNodes );
+							skeleton.ID = nodeID;
+							if ( relationships.parents.length > 1 ) console.warn( 'THREE.FBXLoader: skeleton attached to more than one geometry is not supported.' );
+							skeleton.geometryID = relationships.parents[ 0 ].ID;
+							skeletons[ nodeID ] = skeleton;
 
-				var rawBone = {
+						} else if ( deformerNode.attrType === 'BlendShape' ) {
 
-					ID: child.ID,
-					indices: [],
-					weights: [],
-					transformLink: new THREE.Matrix4().fromArray( boneNode.TransformLink.a ),
-					// transform: new THREE.Matrix4().fromArray( boneNode.Transform.a ),
-					// linkMode: boneNode.Mode,
+							var morphTarget = {
+								id: nodeID
+							};
+							morphTarget.rawTargets = this.parseMorphTargets( relationships, DeformerNodes );
+							morphTarget.id = nodeID;
+							if ( relationships.parents.length > 1 ) console.warn( 'THREE.FBXLoader: morph target attached to more than one geometry is not supported.' );
+							morphTargets[ nodeID ] = morphTarget;
 
-				};
-
-				if ( 'Indexes' in boneNode ) {
+						}
 
-					rawBone.indices = boneNode.Indexes.a;
-					rawBone.weights = boneNode.Weights.a;
+					}
 
 				}
 
-				rawBones.push( rawBone );
-
-			} );
-
-			return {
-
-				rawBones: rawBones,
-				bones: []
-
-			};
+				return {
+					skeletons: skeletons,
+					morphTargets: morphTargets
+				};
 
-		},
+			},
+			// Parse single nodes in FBXTree.Objects.Deformer
+			// The top level skeleton node has type 'Skin' and sub nodes have type 'Cluster'
+			// Each skin node represents a skeleton and each cluster node represents a bone
+			parseSkeleton: function ( relationships, deformerNodes ) {
 
-		// The top level morph deformer node has type "BlendShape" and sub nodes have type "BlendShapeChannel"
-		parseMorphTargets: function ( relationships, deformerNodes ) {
+				var rawBones = [];
+				relationships.children.forEach( function ( child ) {
 
-			var rawMorphTargets = [];
+					var boneNode = deformerNodes[ child.ID ];
+					if ( boneNode.attrType !== 'Cluster' ) return;
+					var rawBone = {
+						ID: child.ID,
+						indices: [],
+						weights: [],
+						transformLink: new THREE.Matrix4().fromArray( boneNode.TransformLink.a ) // transform: new THREE.Matrix4().fromArray( boneNode.Transform.a ),
+						// linkMode: boneNode.Mode,
 
-			for ( var i = 0; i < relationships.children.length; i ++ ) {
+					};
 
-				var child = relationships.children[ i ];
+					if ( 'Indexes' in boneNode ) {
 
-				var morphTargetNode = deformerNodes[ child.ID ];
+						rawBone.indices = boneNode.Indexes.a;
+						rawBone.weights = boneNode.Weights.a;
 
-				var rawMorphTarget = {
+					}
 
-					name: morphTargetNode.attrName,
-					initialWeight: morphTargetNode.DeformPercent,
-					id: morphTargetNode.id,
-					fullWeights: morphTargetNode.FullWeights.a
+					rawBones.push( rawBone );
 
+				} );
+				return {
+					rawBones: rawBones,
+					bones: []
 				};
 
-				if ( morphTargetNode.attrType !== 'BlendShapeChannel' ) return;
-
-				rawMorphTarget.geoID = connections.get( parseInt( child.ID ) ).children.filter( function ( child ) {
+			},
+			// The top level morph deformer node has type "BlendShape" and sub nodes have type "BlendShapeChannel"
+			parseMorphTargets: function ( relationships, deformerNodes ) {
 
-					return child.relationship === undefined;
+				var rawMorphTargets = [];
 
-				} )[ 0 ].ID;
+				for ( var i = 0; i < relationships.children.length; i ++ ) {
 
-				rawMorphTargets.push( rawMorphTarget );
+					var child = relationships.children[ i ];
+					var morphTargetNode = deformerNodes[ child.ID ];
+					var rawMorphTarget = {
+						name: morphTargetNode.attrName,
+						initialWeight: morphTargetNode.DeformPercent,
+						id: morphTargetNode.id,
+						fullWeights: morphTargetNode.FullWeights.a
+					};
+					if ( morphTargetNode.attrType !== 'BlendShapeChannel' ) return;
+					rawMorphTarget.geoID = connections.get( parseInt( child.ID ) ).children.filter( function ( child ) {
 
-			}
+						return child.relationship === undefined;
 
-			return rawMorphTargets;
+					} )[ 0 ].ID;
+					rawMorphTargets.push( rawMorphTarget );
 
-		},
+				}
 
-		// create the main THREE.Group() to be returned by the loader
-		parseScene: function ( deformers, geometryMap, materialMap ) {
+				return rawMorphTargets;
 
-			sceneGraph = new THREE.Group();
+			},
+			// create the main THREE.Group() to be returned by the loader
+			parseScene: function ( deformers, geometryMap, materialMap ) {
 
-			var modelMap = this.parseModels( deformers.skeletons, geometryMap, materialMap );
+				sceneGraph = new THREE.Group();
+				var modelMap = this.parseModels( deformers.skeletons, geometryMap, materialMap );
+				var modelNodes = fbxTree.Objects.Model;
+				var scope = this;
+				modelMap.forEach( function ( model ) {
 
-			var modelNodes = fbxTree.Objects.Model;
+					var modelNode = modelNodes[ model.ID ];
+					scope.setLookAtProperties( model, modelNode );
+					var parentConnections = connections.get( model.ID ).parents;
+					parentConnections.forEach( function ( connection ) {
 
-			var scope = this;
-			modelMap.forEach( function ( model ) {
+						var parent = modelMap.get( connection.ID );
+						if ( parent !== undefined ) parent.add( model );
 
-				var modelNode = modelNodes[ model.ID ];
-				scope.setLookAtProperties( model, modelNode );
+					} );
 
-				var parentConnections = connections.get( model.ID ).parents;
+					if ( model.parent === null ) {
 
-				parentConnections.forEach( function ( connection ) {
+						sceneGraph.add( model );
 
-					var parent = modelMap.get( connection.ID );
-					if ( parent !== undefined ) parent.add( model );
+					}
 
 				} );
+				this.bindSkeleton( deformers.skeletons, geometryMap, modelMap );
+				this.createAmbientLight();
+				this.setupMorphMaterials();
+				sceneGraph.traverse( function ( node ) {
 
-				if ( model.parent === null ) {
-
-					sceneGraph.add( model );
-
-				}
-
-
-			} );
-
-			this.bindSkeleton( deformers.skeletons, geometryMap, modelMap );
+					if ( node.userData.transformData ) {
 
-			this.createAmbientLight();
+						if ( node.parent ) {
 
-			this.setupMorphMaterials();
+							node.userData.transformData.parentMatrix = node.parent.matrix;
+							node.userData.transformData.parentMatrixWorld = node.parent.matrixWorld;
 
-			sceneGraph.traverse( function ( node ) {
-
-				if ( node.userData.transformData ) {
-
-					if ( node.parent ) {
+						}
 
-						node.userData.transformData.parentMatrix = node.parent.matrix;
-						node.userData.transformData.parentMatrixWorld = node.parent.matrixWorld;
+						var transform = generateTransform( node.userData.transformData );
+						node.applyMatrix4( transform );
+						node.updateWorldMatrix();
 
 					}
 
-					var transform = generateTransform( node.userData.transformData );
+				} );
+				var animations = new AnimationParser().parse(); // if all the models where already combined in a single group, just return that
+
+				if ( sceneGraph.children.length === 1 && sceneGraph.children[ 0 ].isGroup ) {
 
-					node.applyMatrix4( transform );
-					node.updateWorldMatrix();
+					sceneGraph.children[ 0 ].animations = animations;
+					sceneGraph = sceneGraph.children[ 0 ];
 
 				}
 
-			} );
+				sceneGraph.animations = animations;
 
-			var animations = new AnimationParser().parse();
+			},
+			// parse nodes in FBXTree.Objects.Model
+			parseModels: function ( skeletons, geometryMap, materialMap ) {
 
-			// if all the models where already combined in a single group, just return that
-			if ( sceneGraph.children.length === 1 && sceneGraph.children[ 0 ].isGroup ) {
+				var modelMap = new Map();
+				var modelNodes = fbxTree.Objects.Model;
 
-				sceneGraph.children[ 0 ].animations = animations;
-				sceneGraph = sceneGraph.children[ 0 ];
+				for ( var nodeID in modelNodes ) {
 
-			}
+					var id = parseInt( nodeID );
+					var node = modelNodes[ nodeID ];
+					var relationships = connections.get( id );
+					var model = this.buildSkeleton( relationships, skeletons, id, node.attrName );
 
-			sceneGraph.animations = animations;
+					if ( ! model ) {
 
-		},
+						switch ( node.attrType ) {
 
-		// parse nodes in FBXTree.Objects.Model
-		parseModels: function ( skeletons, geometryMap, materialMap ) {
+							case 'Camera':
+								model = this.createCamera( relationships );
+								break;
 
-			var modelMap = new Map();
-			var modelNodes = fbxTree.Objects.Model;
+							case 'Light':
+								model = this.createLight( relationships );
+								break;
 
-			for ( var nodeID in modelNodes ) {
+							case 'Mesh':
+								model = this.createMesh( relationships, geometryMap, materialMap );
+								break;
 
-				var id = parseInt( nodeID );
-				var node = modelNodes[ nodeID ];
-				var relationships = connections.get( id );
+							case 'NurbsCurve':
+								model = this.createCurve( relationships, geometryMap );
+								break;
 
-				var model = this.buildSkeleton( relationships, skeletons, id, node.attrName );
+							case 'LimbNode':
+							case 'Root':
+								model = new THREE.Bone();
+								break;
 
-				if ( ! model ) {
+							case 'Null':
+							default:
+								model = new THREE.Group();
+								break;
 
-					switch ( node.attrType ) {
+						}
 
-						case 'Camera':
-							model = this.createCamera( relationships );
-							break;
-						case 'Light':
-							model = this.createLight( relationships );
-							break;
-						case 'Mesh':
-							model = this.createMesh( relationships, geometryMap, materialMap );
-							break;
-						case 'NurbsCurve':
-							model = this.createCurve( relationships, geometryMap );
-							break;
-						case 'LimbNode':
-						case 'Root':
-							model = new THREE.Bone();
-							break;
-						case 'Null':
-						default:
-							model = new THREE.Group();
-							break;
+						model.name = node.attrName ? THREE.PropertyBinding.sanitizeNodeName( node.attrName ) : '';
+						model.ID = id;
 
 					}
 
-					model.name = node.attrName ? THREE.PropertyBinding.sanitizeNodeName( node.attrName ) : '';
-
-					model.ID = id;
+					this.getTransformData( model, node );
+					modelMap.set( id, model );
 
 				}
 
-				this.getTransformData( model, node );
-				modelMap.set( id, model );
-
-			}
-
-			return modelMap;
-
-		},
-
-		buildSkeleton: function ( relationships, skeletons, id, name ) {
-
-			var bone = null;
-
-			relationships.parents.forEach( function ( parent ) {
-
-				for ( var ID in skeletons ) {
-
-					var skeleton = skeletons[ ID ];
-
-					skeleton.rawBones.forEach( function ( rawBone, i ) {
-
-						if ( rawBone.ID === parent.ID ) {
-
-							var subBone = bone;
-							bone = new THREE.Bone();
-
-							bone.matrixWorld.copy( rawBone.transformLink );
-
-							// set name and id here - otherwise in cases where "subBone" is created it will not have a name / id
-
-							bone.name = name ? THREE.PropertyBinding.sanitizeNodeName( name ) : '';
-							bone.ID = id;
-
-							skeleton.bones[ i ] = bone;
-
-							// In cases where a bone is shared between multiple meshes
-							// duplicate the bone here and and it as a child of the first bone
-							if ( subBone !== null ) {
-
-								bone.add( subBone );
-
-							}
-
-						}
-
-					} );
-
-				}
-
-			} );
-
-			return bone;
-
-		},
-
-		// create a THREE.PerspectiveCamera or THREE.OrthographicCamera
-		createCamera: function ( relationships ) {
-
-			var model;
-			var cameraAttribute;
-
-			relationships.children.forEach( function ( child ) {
-
-				var attr = fbxTree.Objects.NodeAttribute[ child.ID ];
-
-				if ( attr !== undefined ) {
-
-					cameraAttribute = attr;
-
-				}
-
-			} );
-
-			if ( cameraAttribute === undefined ) {
-
-				model = new THREE.Object3D();
-
-			} else {
-
-				var type = 0;
-				if ( cameraAttribute.CameraProjectionType !== undefined && cameraAttribute.CameraProjectionType.value === 1 ) {
-
-					type = 1;
-
-				}
-
-				var nearClippingPlane = 1;
-				if ( cameraAttribute.NearPlane !== undefined ) {
-
-					nearClippingPlane = cameraAttribute.NearPlane.value / 1000;
-
-				}
-
-				var farClippingPlane = 1000;
-				if ( cameraAttribute.FarPlane !== undefined ) {
-
-					farClippingPlane = cameraAttribute.FarPlane.value / 1000;
-
-				}
-
-
-				var width = window.innerWidth;
-				var height = window.innerHeight;
-
-				if ( cameraAttribute.AspectWidth !== undefined && cameraAttribute.AspectHeight !== undefined ) {
-
-					width = cameraAttribute.AspectWidth.value;
-					height = cameraAttribute.AspectHeight.value;
-
-				}
-
-				var aspect = width / height;
-
-				var fov = 45;
-				if ( cameraAttribute.FieldOfView !== undefined ) {
-
-					fov = cameraAttribute.FieldOfView.value;
-
-				}
-
-				var focalLength = cameraAttribute.FocalLength ? cameraAttribute.FocalLength.value : null;
-
-				switch ( type ) {
-
-					case 0: // Perspective
-						model = new THREE.PerspectiveCamera( fov, aspect, nearClippingPlane, farClippingPlane );
-						if ( focalLength !== null ) model.setFocalLength( focalLength );
-						break;
-
-					case 1: // Orthographic
-						model = new THREE.OrthographicCamera( - width / 2, width / 2, height / 2, - height / 2, nearClippingPlane, farClippingPlane );
-						break;
-
-					default:
-						console.warn( 'THREE.FBXLoader: Unknown camera type ' + type + '.' );
-						model = new THREE.Object3D();
-						break;
-
-				}
-
-			}
-
-			return model;
-
-		},
-
-		// Create a THREE.DirectionalLight, THREE.PointLight or THREE.SpotLight
-		createLight: function ( relationships ) {
-
-			var model;
-			var lightAttribute;
-
-			relationships.children.forEach( function ( child ) {
-
-				var attr = fbxTree.Objects.NodeAttribute[ child.ID ];
-
-				if ( attr !== undefined ) {
-
-					lightAttribute = attr;
-
-				}
-
-			} );
-
-			if ( lightAttribute === undefined ) {
-
-				model = new THREE.Object3D();
-
-			} else {
-
-				var type;
-
-				// LightType can be undefined for Point lights
-				if ( lightAttribute.LightType === undefined ) {
-
-					type = 0;
-
-				} else {
-
-					type = lightAttribute.LightType.value;
-
-				}
-
-				var color = 0xffffff;
-
-				if ( lightAttribute.Color !== undefined ) {
-
-					color = new THREE.Color().fromArray( lightAttribute.Color.value );
-
-				}
-
-				var intensity = ( lightAttribute.Intensity === undefined ) ? 1 : lightAttribute.Intensity.value / 100;
-
-				// light disabled
-				if ( lightAttribute.CastLightOnObject !== undefined && lightAttribute.CastLightOnObject.value === 0 ) {
-
-					intensity = 0;
-
-				}
-
-				var distance = 0;
-				if ( lightAttribute.FarAttenuationEnd !== undefined ) {
-
-					if ( lightAttribute.EnableFarAttenuation !== undefined && lightAttribute.EnableFarAttenuation.value === 0 ) {
-
-						distance = 0;
-
-					} else {
-
-						distance = lightAttribute.FarAttenuationEnd.value;
-
-					}
-
-				}
-
-				// TODO: could this be calculated linearly from FarAttenuationStart to FarAttenuationEnd?
-				var decay = 1;
-
-				switch ( type ) {
-
-					case 0: // Point
-						model = new THREE.PointLight( color, intensity, distance, decay );
-						break;
-
-					case 1: // Directional
-						model = new THREE.DirectionalLight( color, intensity );
-						break;
-
-					case 2: // Spot
-						var angle = Math.PI / 3;
-
-						if ( lightAttribute.InnerAngle !== undefined ) {
-
-							angle = THREE.MathUtils.degToRad( lightAttribute.InnerAngle.value );
-
-						}
-
-						var penumbra = 0;
-						if ( lightAttribute.OuterAngle !== undefined ) {
-
-							// TODO: this is not correct - FBX calculates outer and inner angle in degrees
-							// with OuterAngle > InnerAngle && OuterAngle <= Math.PI
-							// while three.js uses a penumbra between (0, 1) to attenuate the inner angle
-							penumbra = THREE.MathUtils.degToRad( lightAttribute.OuterAngle.value );
-							penumbra = Math.max( penumbra, 1 );
-
-						}
-
-						model = new THREE.SpotLight( color, intensity, distance, angle, penumbra, decay );
-						break;
-
-					default:
-						console.warn( 'THREE.FBXLoader: Unknown light type ' + lightAttribute.LightType.value + ', defaulting to a THREE.PointLight.' );
-						model = new THREE.PointLight( color, intensity );
-						break;
-
-				}
-
-				if ( lightAttribute.CastShadows !== undefined && lightAttribute.CastShadows.value === 1 ) {
-
-					model.castShadow = true;
-
-				}
-
-			}
-
-			return model;
-
-		},
-
-		createMesh: function ( relationships, geometryMap, materialMap ) {
-
-			var model;
-			var geometry = null;
-			var material = null;
-			var materials = [];
-
-			// get geometry and materials(s) from connections
-			relationships.children.forEach( function ( child ) {
-
-				if ( geometryMap.has( child.ID ) ) {
-
-					geometry = geometryMap.get( child.ID );
-
-				}
-
-				if ( materialMap.has( child.ID ) ) {
-
-					materials.push( materialMap.get( child.ID ) );
-
-				}
-
-			} );
-
-			if ( materials.length > 1 ) {
-
-				material = materials;
-
-			} else if ( materials.length > 0 ) {
-
-				material = materials[ 0 ];
-
-			} else {
-
-				material = new THREE.MeshPhongMaterial( { color: 0xcccccc } );
-				materials.push( material );
-
-			}
-
-			if ( 'color' in geometry.attributes ) {
-
-				materials.forEach( function ( material ) {
-
-					material.vertexColors = true;
-
-				} );
-
-			}
-
-			if ( geometry.FBX_Deformer ) {
-
-				materials.forEach( function ( material ) {
-
-					material.skinning = true;
-
-				} );
-
-				model = new THREE.SkinnedMesh( geometry, material );
-				model.normalizeSkinWeights();
-
-			} else {
-
-				model = new THREE.Mesh( geometry, material );
-
-			}
-
-			return model;
-
-		},
-
-		createCurve: function ( relationships, geometryMap ) {
-
-			var geometry = relationships.children.reduce( function ( geo, child ) {
-
-				if ( geometryMap.has( child.ID ) ) geo = geometryMap.get( child.ID );
-
-				return geo;
-
-			}, null );
-
-			// FBX does not list materials for Nurbs lines, so we'll just put our own in here.
-			var material = new THREE.LineBasicMaterial( { color: 0x3300ff, linewidth: 1 } );
-			return new THREE.Line( geometry, material );
-
-		},
-
-		// parse the model node for transform data
-		getTransformData: function ( model, modelNode ) {
-
-			var transformData = {};
-
-			if ( 'InheritType' in modelNode ) transformData.inheritType = parseInt( modelNode.InheritType.value );
-
-			if ( 'RotationOrder' in modelNode ) transformData.eulerOrder = getEulerOrder( modelNode.RotationOrder.value );
-			else transformData.eulerOrder = 'ZYX';
-
-			if ( 'Lcl_Translation' in modelNode ) transformData.translation = modelNode.Lcl_Translation.value;
-
-			if ( 'PreRotation' in modelNode ) transformData.preRotation = modelNode.PreRotation.value;
-			if ( 'Lcl_Rotation' in modelNode ) transformData.rotation = modelNode.Lcl_Rotation.value;
-			if ( 'PostRotation' in modelNode ) transformData.postRotation = modelNode.PostRotation.value;
-
-			if ( 'Lcl_Scaling' in modelNode ) transformData.scale = modelNode.Lcl_Scaling.value;
-
-			if ( 'ScalingOffset' in modelNode ) transformData.scalingOffset = modelNode.ScalingOffset.value;
-			if ( 'ScalingPivot' in modelNode ) transformData.scalingPivot = modelNode.ScalingPivot.value;
-
-			if ( 'RotationOffset' in modelNode ) transformData.rotationOffset = modelNode.RotationOffset.value;
-			if ( 'RotationPivot' in modelNode ) transformData.rotationPivot = modelNode.RotationPivot.value;
-
-			model.userData.transformData = transformData;
-
-		},
-
-		setLookAtProperties: function ( model, modelNode ) {
-
-			if ( 'LookAtProperty' in modelNode ) {
-
-				var children = connections.get( model.ID ).children;
-
-				children.forEach( function ( child ) {
-
-					if ( child.relationship === 'LookAtProperty' ) {
-
-						var lookAtTarget = fbxTree.Objects.Model[ child.ID ];
-
-						if ( 'Lcl_Translation' in lookAtTarget ) {
-
-							var pos = lookAtTarget.Lcl_Translation.value;
-
-							// DirectionalLight, SpotLight
-							if ( model.target !== undefined ) {
-
-								model.target.position.fromArray( pos );
-								sceneGraph.add( model.target );
-
-							} else { // Cameras and other Object3Ds
-
-								model.lookAt( new THREE.Vector3().fromArray( pos ) );
-
-							}
-
-						}
-
-					}
-
-				} );
-
-			}
-
-		},
-
-		bindSkeleton: function ( skeletons, geometryMap, modelMap ) {
-
-			var bindMatrices = this.parsePoseNodes();
-
-			for ( var ID in skeletons ) {
-
-				var skeleton = skeletons[ ID ];
-
-				var parents = connections.get( parseInt( skeleton.ID ) ).parents;
-
-				parents.forEach( function ( parent ) {
-
-					if ( geometryMap.has( parent.ID ) ) {
-
-						var geoID = parent.ID;
-						var geoRelationships = connections.get( geoID );
-
-						geoRelationships.parents.forEach( function ( geoConnParent ) {
-
-							if ( modelMap.has( geoConnParent.ID ) ) {
-
-								var model = modelMap.get( geoConnParent.ID );
-
-								model.bind( new THREE.Skeleton( skeleton.bones ), bindMatrices[ geoConnParent.ID ] );
-
-							}
-
-						} );
-
-					}
-
-				} );
-
-			}
-
-		},
-
-		parsePoseNodes: function () {
-
-			var bindMatrices = {};
-
-			if ( 'Pose' in fbxTree.Objects ) {
-
-				var BindPoseNode = fbxTree.Objects.Pose;
-
-				for ( var nodeID in BindPoseNode ) {
-
-					if ( BindPoseNode[ nodeID ].attrType === 'BindPose' ) {
-
-						var poseNodes = BindPoseNode[ nodeID ].PoseNode;
-
-						if ( Array.isArray( poseNodes ) ) {
-
-							poseNodes.forEach( function ( poseNode ) {
-
-								bindMatrices[ poseNode.Node ] = new THREE.Matrix4().fromArray( poseNode.Matrix.a );
-
-							} );
-
-						} else {
-
-							bindMatrices[ poseNodes.Node ] = new THREE.Matrix4().fromArray( poseNodes.Matrix.a );
-
-						}
-
-					}
-
-				}
-
-			}
-
-			return bindMatrices;
-
-		},
-
-		// Parse ambient color in FBXTree.GlobalSettings - if it's not set to black (default), create an ambient light
-		createAmbientLight: function () {
-
-			if ( 'GlobalSettings' in fbxTree && 'AmbientColor' in fbxTree.GlobalSettings ) {
-
-				var ambientColor = fbxTree.GlobalSettings.AmbientColor.value;
-				var r = ambientColor[ 0 ];
-				var g = ambientColor[ 1 ];
-				var b = ambientColor[ 2 ];
-
-				if ( r !== 0 || g !== 0 || b !== 0 ) {
-
-					var color = new THREE.Color( r, g, b );
-					sceneGraph.add( new THREE.AmbientLight( color, 1 ) );
-
-				}
-
-			}
-
-		},
-
-		setupMorphMaterials: function () {
-
-			var scope = this;
-			sceneGraph.traverse( function ( child ) {
-
-				if ( child.isMesh ) {
-
-					if ( child.geometry.morphAttributes.position && child.geometry.morphAttributes.position.length ) {
-
-						if ( Array.isArray( child.material ) ) {
-
-							child.material.forEach( function ( material, i ) {
-
-								scope.setupMorphMaterial( child, material, i );
-
-							} );
-
-						} else {
-
-							scope.setupMorphMaterial( child, child.material );
-
-						}
-
-					}
-
-				}
-
-			} );
-
-		},
+				return modelMap;
 
-		setupMorphMaterial: function ( child, material, index ) {
+			},
+			buildSkeleton: function ( relationships, skeletons, id, name ) {
 
-			var uuid = child.uuid;
-			var matUuid = material.uuid;
+				var bone = null;
+				relationships.parents.forEach( function ( parent ) {
 
-			// if a geometry has morph targets, it cannot share the material with other geometries
-			var sharedMat = false;
+					for ( var ID in skeletons ) {
 
-			sceneGraph.traverse( function ( node ) {
+						var skeleton = skeletons[ ID ];
+						skeleton.rawBones.forEach( function ( rawBone, i ) {
 
-				if ( node.isMesh ) {
+							if ( rawBone.ID === parent.ID ) {
 
-					if ( Array.isArray( node.material ) ) {
+								var subBone = bone;
+								bone = new THREE.Bone();
+								bone.matrixWorld.copy( rawBone.transformLink ); // set name and id here - otherwise in cases where "subBone" is created it will not have a name / id
 
-						node.material.forEach( function ( mat ) {
+								bone.name = name ? THREE.PropertyBinding.sanitizeNodeName( name ) : '';
+								bone.ID = id;
+								skeleton.bones[ i ] = bone; // In cases where a bone is shared between multiple meshes
+								// duplicate the bone here and and it as a child of the first bone
 
-							if ( mat.uuid === matUuid && node.uuid !== uuid ) sharedMat = true;
+								if ( subBone !== null ) {
 
-						} );
+									bone.add( subBone );
 
-					} else if ( node.material.uuid === matUuid && node.uuid !== uuid ) sharedMat = true;
+								}
 
-				}
+							}
 
-			} );
+						} );
 
-			if ( sharedMat === true ) {
+					}
 
-				var clonedMat = material.clone();
-				clonedMat.morphTargets = true;
+				} );
+				return bone;
 
-				if ( index === undefined ) child.material = clonedMat;
-				else child.material[ index ] = clonedMat;
+			},
+			// create a THREE.PerspectiveCamera or THREE.OrthographicCamera
+			createCamera: function ( relationships ) {
 
-			} else material.morphTargets = true;
+				var model;
+				var cameraAttribute;
+				relationships.children.forEach( function ( child ) {
 
-		}
+					var attr = fbxTree.Objects.NodeAttribute[ child.ID ];
 
-	};
+					if ( attr !== undefined ) {
 
-	// parse Geometry data from FBXTree and return map of BufferGeometries
-	function GeometryParser() {}
+						cameraAttribute = attr;
 
-	GeometryParser.prototype = {
+					}
 
-		constructor: GeometryParser,
+				} );
 
-		// Parse nodes in FBXTree.Objects.Geometry
-		parse: function ( deformers ) {
+				if ( cameraAttribute === undefined ) {
 
-			var geometryMap = new Map();
+					model = new THREE.Object3D();
 
-			if ( 'Geometry' in fbxTree.Objects ) {
+				} else {
 
-				var geoNodes = fbxTree.Objects.Geometry;
+					var type = 0;
 
-				for ( var nodeID in geoNodes ) {
+					if ( cameraAttribute.CameraProjectionType !== undefined && cameraAttribute.CameraProjectionType.value === 1 ) {
 
-					var relationships = connections.get( parseInt( nodeID ) );
-					var geo = this.parseGeometry( relationships, geoNodes[ nodeID ], deformers );
+						type = 1;
 
-					geometryMap.set( parseInt( nodeID ), geo );
+					}
 
-				}
+					var nearClippingPlane = 1;
 
-			}
+					if ( cameraAttribute.NearPlane !== undefined ) {
 
-			return geometryMap;
+						nearClippingPlane = cameraAttribute.NearPlane.value / 1000;
 
-		},
+					}
 
-		// Parse single node in FBXTree.Objects.Geometry
-		parseGeometry: function ( relationships, geoNode, deformers ) {
+					var farClippingPlane = 1000;
 
-			switch ( geoNode.attrType ) {
+					if ( cameraAttribute.FarPlane !== undefined ) {
 
-				case 'Mesh':
-					return this.parseMeshGeometry( relationships, geoNode, deformers );
-					break;
+						farClippingPlane = cameraAttribute.FarPlane.value / 1000;
 
-				case 'NurbsCurve':
-					return this.parseNurbsGeometry( geoNode );
-					break;
+					}
 
-			}
+					var width = window.innerWidth;
+					var height = window.innerHeight;
 
-		},
+					if ( cameraAttribute.AspectWidth !== undefined && cameraAttribute.AspectHeight !== undefined ) {
 
+						width = cameraAttribute.AspectWidth.value;
+						height = cameraAttribute.AspectHeight.value;
 
-		// Parse single node mesh geometry in FBXTree.Objects.Geometry
-		parseMeshGeometry: function ( relationships, geoNode, deformers ) {
+					}
 
-			var skeletons = deformers.skeletons;
-			var morphTargets = [];
+					var aspect = width / height;
+					var fov = 45;
 
-			var modelNodes = relationships.parents.map( function ( parent ) {
+					if ( cameraAttribute.FieldOfView !== undefined ) {
 
-				return fbxTree.Objects.Model[ parent.ID ];
+						fov = cameraAttribute.FieldOfView.value;
 
-			} );
+					}
 
-			// don't create geometry if it is not associated with any models
-			if ( modelNodes.length === 0 ) return;
+					var focalLength = cameraAttribute.FocalLength ? cameraAttribute.FocalLength.value : null;
 
-			var skeleton = relationships.children.reduce( function ( skeleton, child ) {
+					switch ( type ) {
 
-				if ( skeletons[ child.ID ] !== undefined ) skeleton = skeletons[ child.ID ];
+						case 0:
+						// Perspective
+							model = new THREE.PerspectiveCamera( fov, aspect, nearClippingPlane, farClippingPlane );
+							if ( focalLength !== null ) model.setFocalLength( focalLength );
+							break;
 
-				return skeleton;
+						case 1:
+						// Orthographic
+							model = new THREE.OrthographicCamera( - width / 2, width / 2, height / 2, - height / 2, nearClippingPlane, farClippingPlane );
+							break;
 
-			}, null );
+						default:
+							console.warn( 'THREE.FBXLoader: Unknown camera type ' + type + '.' );
+							model = new THREE.Object3D();
+							break;
 
-			relationships.children.forEach( function ( child ) {
+					}
 
-				if ( deformers.morphTargets[ child.ID ] !== undefined ) {
+				}
 
-					morphTargets.push( deformers.morphTargets[ child.ID ] );
+				return model;
 
-				}
+			},
+			// Create a THREE.DirectionalLight, THREE.PointLight or THREE.SpotLight
+			createLight: function ( relationships ) {
 
-			} );
+				var model;
+				var lightAttribute;
+				relationships.children.forEach( function ( child ) {
 
-			// Assume one model and get the preRotation from that
-			// if there is more than one model associated with the geometry this may cause problems
-			var modelNode = modelNodes[ 0 ];
+					var attr = fbxTree.Objects.NodeAttribute[ child.ID ];
 
-			var transformData = {};
+					if ( attr !== undefined ) {
 
-			if ( 'RotationOrder' in modelNode ) transformData.eulerOrder = getEulerOrder( modelNode.RotationOrder.value );
-			if ( 'InheritType' in modelNode ) transformData.inheritType = parseInt( modelNode.InheritType.value );
+						lightAttribute = attr;
 
-			if ( 'GeometricTranslation' in modelNode ) transformData.translation = modelNode.GeometricTranslation.value;
-			if ( 'GeometricRotation' in modelNode ) transformData.rotation = modelNode.GeometricRotation.value;
-			if ( 'GeometricScaling' in modelNode ) transformData.scale = modelNode.GeometricScaling.value;
+					}
 
-			var transform = generateTransform( transformData );
+				} );
 
-			return this.genGeometry( geoNode, skeleton, morphTargets, transform );
+				if ( lightAttribute === undefined ) {
 
-		},
+					model = new THREE.Object3D();
 
-		// Generate a THREE.BufferGeometry from a node in FBXTree.Objects.Geometry
-		genGeometry: function ( geoNode, skeleton, morphTargets, preTransform ) {
+				} else {
 
-			var geo = new THREE.BufferGeometry();
-			if ( geoNode.attrName ) geo.name = geoNode.attrName;
+					var type; // LightType can be undefined for Point lights
 
-			var geoInfo = this.parseGeoNode( geoNode, skeleton );
-			var buffers = this.genBuffers( geoInfo );
+					if ( lightAttribute.LightType === undefined ) {
 
-			var positionAttribute = new THREE.Float32BufferAttribute( buffers.vertex, 3 );
+						type = 0;
 
-			positionAttribute.applyMatrix4( preTransform );
+					} else {
 
-			geo.setAttribute( 'position', positionAttribute );
+						type = lightAttribute.LightType.value;
 
-			if ( buffers.colors.length > 0 ) {
+					}
 
-				geo.setAttribute( 'color', new THREE.Float32BufferAttribute( buffers.colors, 3 ) );
+					var color = 0xffffff;
 
-			}
+					if ( lightAttribute.Color !== undefined ) {
 
-			if ( skeleton ) {
+						color = new THREE.Color().fromArray( lightAttribute.Color.value );
 
-				geo.setAttribute( 'skinIndex', new THREE.Uint16BufferAttribute( buffers.weightsIndices, 4 ) );
+					}
 
-				geo.setAttribute( 'skinWeight', new THREE.Float32BufferAttribute( buffers.vertexWeights, 4 ) );
+					var intensity = lightAttribute.Intensity === undefined ? 1 : lightAttribute.Intensity.value / 100; // light disabled
 
-				// used later to bind the skeleton to the model
-				geo.FBX_Deformer = skeleton;
+					if ( lightAttribute.CastLightOnObject !== undefined && lightAttribute.CastLightOnObject.value === 0 ) {
 
-			}
+						intensity = 0;
 
-			if ( buffers.normal.length > 0 ) {
+					}
 
-				var normalMatrix = new THREE.Matrix3().getNormalMatrix( preTransform );
+					var distance = 0;
 
-				var normalAttribute = new THREE.Float32BufferAttribute( buffers.normal, 3 );
-				normalAttribute.applyNormalMatrix( normalMatrix );
+					if ( lightAttribute.FarAttenuationEnd !== undefined ) {
 
-				geo.setAttribute( 'normal', normalAttribute );
+						if ( lightAttribute.EnableFarAttenuation !== undefined && lightAttribute.EnableFarAttenuation.value === 0 ) {
 
-			}
+							distance = 0;
 
-			buffers.uvs.forEach( function ( uvBuffer, i ) {
+						} else {
 
-				// subsequent uv buffers are called 'uv1', 'uv2', ...
-				var name = 'uv' + ( i + 1 ).toString();
+							distance = lightAttribute.FarAttenuationEnd.value;
 
-				// the first uv buffer is just called 'uv'
-				if ( i === 0 ) {
+						}
 
-					name = 'uv';
+					} // TODO: could this be calculated linearly from FarAttenuationStart to FarAttenuationEnd?
 
-				}
 
-				geo.setAttribute( name, new THREE.Float32BufferAttribute( buffers.uvs[ i ], 2 ) );
+					var decay = 1;
 
-			} );
+					switch ( type ) {
 
-			if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) {
+						case 0:
+						// Point
+							model = new THREE.PointLight( color, intensity, distance, decay );
+							break;
 
-				// Convert the material indices of each vertex into rendering groups on the geometry.
-				var prevMaterialIndex = buffers.materialIndex[ 0 ];
-				var startIndex = 0;
+						case 1:
+						// Directional
+							model = new THREE.DirectionalLight( color, intensity );
+							break;
 
-				buffers.materialIndex.forEach( function ( currentIndex, i ) {
+						case 2:
+						// Spot
+							var angle = Math.PI / 3;
 
-					if ( currentIndex !== prevMaterialIndex ) {
+							if ( lightAttribute.InnerAngle !== undefined ) {
 
-						geo.addGroup( startIndex, i - startIndex, prevMaterialIndex );
+								angle = THREE.MathUtils.degToRad( lightAttribute.InnerAngle.value );
 
-						prevMaterialIndex = currentIndex;
-						startIndex = i;
+							}
 
-					}
+							var penumbra = 0;
 
-				} );
+							if ( lightAttribute.OuterAngle !== undefined ) {
 
-				// the loop above doesn't add the last group, do that here.
-				if ( geo.groups.length > 0 ) {
+								// TODO: this is not correct - FBX calculates outer and inner angle in degrees
+								// with OuterAngle > InnerAngle && OuterAngle <= Math.PI
+								// while three.js uses a penumbra between (0, 1) to attenuate the inner angle
+								penumbra = THREE.MathUtils.degToRad( lightAttribute.OuterAngle.value );
+								penumbra = Math.max( penumbra, 1 );
 
-					var lastGroup = geo.groups[ geo.groups.length - 1 ];
-					var lastIndex = lastGroup.start + lastGroup.count;
+							}
 
-					if ( lastIndex !== buffers.materialIndex.length ) {
+							model = new THREE.SpotLight( color, intensity, distance, angle, penumbra, decay );
+							break;
 
-						geo.addGroup( lastIndex, buffers.materialIndex.length - lastIndex, prevMaterialIndex );
+						default:
+							console.warn( 'THREE.FBXLoader: Unknown light type ' + lightAttribute.LightType.value + ', defaulting to a THREE.PointLight.' );
+							model = new THREE.PointLight( color, intensity );
+							break;
 
 					}
 
-				}
+					if ( lightAttribute.CastShadows !== undefined && lightAttribute.CastShadows.value === 1 ) {
 
-				// case where there are multiple materials but the whole geometry is only
-				// using one of them
-				if ( geo.groups.length === 0 ) {
+						model.castShadow = true;
 
-					geo.addGroup( 0, buffers.materialIndex.length, buffers.materialIndex[ 0 ] );
+					}
 
 				}
 
-			}
-
-			this.addMorphTargets( geo, geoNode, morphTargets, preTransform );
+				return model;
 
-			return geo;
+			},
+			createMesh: function ( relationships, geometryMap, materialMap ) {
 
-		},
+				var model;
+				var geometry = null;
+				var material = null;
+				var materials = []; // get geometry and materials(s) from connections
 
-		parseGeoNode: function ( geoNode, skeleton ) {
+				relationships.children.forEach( function ( child ) {
 
-			var geoInfo = {};
+					if ( geometryMap.has( child.ID ) ) {
 
-			geoInfo.vertexPositions = ( geoNode.Vertices !== undefined ) ? geoNode.Vertices.a : [];
-			geoInfo.vertexIndices = ( geoNode.PolygonVertexIndex !== undefined ) ? geoNode.PolygonVertexIndex.a : [];
+						geometry = geometryMap.get( child.ID );
 
-			if ( geoNode.LayerElementColor ) {
+					}
 
-				geoInfo.color = this.parseVertexColors( geoNode.LayerElementColor[ 0 ] );
+					if ( materialMap.has( child.ID ) ) {
 
-			}
+						materials.push( materialMap.get( child.ID ) );
 
-			if ( geoNode.LayerElementMaterial ) {
+					}
 
-				geoInfo.material = this.parseMaterialIndices( geoNode.LayerElementMaterial[ 0 ] );
+				} );
 
-			}
+				if ( materials.length > 1 ) {
 
-			if ( geoNode.LayerElementNormal ) {
+					material = materials;
 
-				geoInfo.normal = this.parseNormals( geoNode.LayerElementNormal[ 0 ] );
+				} else if ( materials.length > 0 ) {
 
-			}
+					material = materials[ 0 ];
 
-			if ( geoNode.LayerElementUV ) {
+				} else {
 
-				geoInfo.uv = [];
+					material = new THREE.MeshPhongMaterial( {
+						color: 0xcccccc
+					} );
+					materials.push( material );
 
-				var i = 0;
-				while ( geoNode.LayerElementUV[ i ] ) {
+				}
 
-					if ( geoNode.LayerElementUV[ i ].UV ) {
+				if ( 'color' in geometry.attributes ) {
 
-						geoInfo.uv.push( this.parseUVs( geoNode.LayerElementUV[ i ] ) );
+					materials.forEach( function ( material ) {
 
-					}
+						material.vertexColors = true;
 
-					i ++;
+					} );
 
 				}
 
-			}
+				if ( geometry.FBX_Deformer ) {
 
-			geoInfo.weightTable = {};
+					materials.forEach( function ( material ) {
 
-			if ( skeleton !== null ) {
+						material.skinning = true;
 
-				geoInfo.skeleton = skeleton;
+					} );
+					model = new THREE.SkinnedMesh( geometry, material );
+					model.normalizeSkinWeights();
 
-				skeleton.rawBones.forEach( function ( rawBone, i ) {
+				} else {
 
-					// loop over the bone's vertex indices and weights
-					rawBone.indices.forEach( function ( index, j ) {
+					model = new THREE.Mesh( geometry, material );
 
-						if ( geoInfo.weightTable[ index ] === undefined ) geoInfo.weightTable[ index ] = [];
+				}
 
-						geoInfo.weightTable[ index ].push( {
+				return model;
 
-							id: i,
-							weight: rawBone.weights[ j ],
+			},
+			createCurve: function ( relationships, geometryMap ) {
 
-						} );
+				var geometry = relationships.children.reduce( function ( geo, child ) {
 
-					} );
+					if ( geometryMap.has( child.ID ) ) geo = geometryMap.get( child.ID );
+					return geo;
+
+				}, null ); // FBX does not list materials for Nurbs lines, so we'll just put our own in here.
 
+				var material = new THREE.LineBasicMaterial( {
+					color: 0x3300ff,
+					linewidth: 1
 				} );
+				return new THREE.Line( geometry, material );
 
-			}
+			},
+			// parse the model node for transform data
+			getTransformData: function ( model, modelNode ) {
 
-			return geoInfo;
+				var transformData = {};
+				if ( 'InheritType' in modelNode ) transformData.inheritType = parseInt( modelNode.InheritType.value );
+				if ( 'RotationOrder' in modelNode ) transformData.eulerOrder = getEulerOrder( modelNode.RotationOrder.value ); else transformData.eulerOrder = 'ZYX';
+				if ( 'Lcl_Translation' in modelNode ) transformData.translation = modelNode.Lcl_Translation.value;
+				if ( 'PreRotation' in modelNode ) transformData.preRotation = modelNode.PreRotation.value;
+				if ( 'Lcl_Rotation' in modelNode ) transformData.rotation = modelNode.Lcl_Rotation.value;
+				if ( 'PostRotation' in modelNode ) transformData.postRotation = modelNode.PostRotation.value;
+				if ( 'Lcl_Scaling' in modelNode ) transformData.scale = modelNode.Lcl_Scaling.value;
+				if ( 'ScalingOffset' in modelNode ) transformData.scalingOffset = modelNode.ScalingOffset.value;
+				if ( 'ScalingPivot' in modelNode ) transformData.scalingPivot = modelNode.ScalingPivot.value;
+				if ( 'RotationOffset' in modelNode ) transformData.rotationOffset = modelNode.RotationOffset.value;
+				if ( 'RotationPivot' in modelNode ) transformData.rotationPivot = modelNode.RotationPivot.value;
+				model.userData.transformData = transformData;
 
-		},
+			},
+			setLookAtProperties: function ( model, modelNode ) {
 
-		genBuffers: function ( geoInfo ) {
+				if ( 'LookAtProperty' in modelNode ) {
 
-			var buffers = {
-				vertex: [],
-				normal: [],
-				colors: [],
-				uvs: [],
-				materialIndex: [],
-				vertexWeights: [],
-				weightsIndices: [],
-			};
+					var children = connections.get( model.ID ).children;
+					children.forEach( function ( child ) {
 
-			var polygonIndex = 0;
-			var faceLength = 0;
-			var displayedWeightsWarning = false;
+						if ( child.relationship === 'LookAtProperty' ) {
 
-			// these will hold data for a single face
-			var facePositionIndexes = [];
-			var faceNormals = [];
-			var faceColors = [];
-			var faceUVs = [];
-			var faceWeights = [];
-			var faceWeightIndices = [];
+							var lookAtTarget = fbxTree.Objects.Model[ child.ID ];
 
-			var scope = this;
-			geoInfo.vertexIndices.forEach( function ( vertexIndex, polygonVertexIndex ) {
+							if ( 'Lcl_Translation' in lookAtTarget ) {
 
-				var endOfFace = false;
+								var pos = lookAtTarget.Lcl_Translation.value; // THREE.DirectionalLight, THREE.SpotLight
 
-				// Face index and vertex index arrays are combined in a single array
-				// A cube with quad faces looks like this:
-				// PolygonVertexIndex: *24 {
-				//  a: 0, 1, 3, -3, 2, 3, 5, -5, 4, 5, 7, -7, 6, 7, 1, -1, 1, 7, 5, -4, 6, 0, 2, -5
-				//  }
-				// Negative numbers mark the end of a face - first face here is 0, 1, 3, -3
-				// to find index of last vertex bit shift the index: ^ - 1
-				if ( vertexIndex < 0 ) {
+								if ( model.target !== undefined ) {
 
-					vertexIndex = vertexIndex ^ - 1; // equivalent to ( x * -1 ) - 1
-					endOfFace = true;
+									model.target.position.fromArray( pos );
+									sceneGraph.add( model.target );
 
-				}
+								} else {
 
-				var weightIndices = [];
-				var weights = [];
+									// Cameras and other Object3Ds
+									model.lookAt( new THREE.Vector3().fromArray( pos ) );
 
-				facePositionIndexes.push( vertexIndex * 3, vertexIndex * 3 + 1, vertexIndex * 3 + 2 );
+								}
 
-				if ( geoInfo.color ) {
+							}
 
-					var data = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.color );
+						}
 
-					faceColors.push( data[ 0 ], data[ 1 ], data[ 2 ] );
+					} );
 
 				}
 
-				if ( geoInfo.skeleton ) {
+			},
+			bindSkeleton: function ( skeletons, geometryMap, modelMap ) {
 
-					if ( geoInfo.weightTable[ vertexIndex ] !== undefined ) {
+				var bindMatrices = this.parsePoseNodes();
 
-						geoInfo.weightTable[ vertexIndex ].forEach( function ( wt ) {
+				for ( var ID in skeletons ) {
 
-							weights.push( wt.weight );
-							weightIndices.push( wt.id );
+					var skeleton = skeletons[ ID ];
+					var parents = connections.get( parseInt( skeleton.ID ) ).parents;
+					parents.forEach( function ( parent ) {
 
-						} );
+						if ( geometryMap.has( parent.ID ) ) {
 
+							var geoID = parent.ID;
+							var geoRelationships = connections.get( geoID );
+							geoRelationships.parents.forEach( function ( geoConnParent ) {
 
-					}
+								if ( modelMap.has( geoConnParent.ID ) ) {
 
-					if ( weights.length > 4 ) {
+									var model = modelMap.get( geoConnParent.ID );
+									model.bind( new THREE.Skeleton( skeleton.bones ), bindMatrices[ geoConnParent.ID ] );
 
-						if ( ! displayedWeightsWarning ) {
+								}
 
-							console.warn( 'THREE.FBXLoader: Vertex has more than 4 skinning weights assigned to vertex. Deleting additional weights.' );
-							displayedWeightsWarning = true;
+							} );
 
 						}
 
-						var wIndex = [ 0, 0, 0, 0 ];
-						var Weight = [ 0, 0, 0, 0 ];
+					} );
 
-						weights.forEach( function ( weight, weightIndex ) {
+				}
 
-							var currentWeight = weight;
-							var currentIndex = weightIndices[ weightIndex ];
+			},
+			parsePoseNodes: function () {
 
-							Weight.forEach( function ( comparedWeight, comparedWeightIndex, comparedWeightArray ) {
+				var bindMatrices = {};
 
-								if ( currentWeight > comparedWeight ) {
+				if ( 'Pose' in fbxTree.Objects ) {
 
-									comparedWeightArray[ comparedWeightIndex ] = currentWeight;
-									currentWeight = comparedWeight;
+					var BindPoseNode = fbxTree.Objects.Pose;
 
-									var tmp = wIndex[ comparedWeightIndex ];
-									wIndex[ comparedWeightIndex ] = currentIndex;
-									currentIndex = tmp;
+					for ( var nodeID in BindPoseNode ) {
 
-								}
+						if ( BindPoseNode[ nodeID ].attrType === 'BindPose' ) {
 
-							} );
+							var poseNodes = BindPoseNode[ nodeID ].PoseNode;
 
-						} );
+							if ( Array.isArray( poseNodes ) ) {
 
-						weightIndices = wIndex;
-						weights = Weight;
+								poseNodes.forEach( function ( poseNode ) {
 
-					}
+									bindMatrices[ poseNode.Node ] = new THREE.Matrix4().fromArray( poseNode.Matrix.a );
 
-					// if the weight array is shorter than 4 pad with 0s
-					while ( weights.length < 4 ) {
+								} );
 
-						weights.push( 0 );
-						weightIndices.push( 0 );
+							} else {
 
-					}
+								bindMatrices[ poseNodes.Node ] = new THREE.Matrix4().fromArray( poseNodes.Matrix.a );
 
-					for ( var i = 0; i < 4; ++ i ) {
+							}
 
-						faceWeights.push( weights[ i ] );
-						faceWeightIndices.push( weightIndices[ i ] );
+						}
 
 					}
 
 				}
 
-				if ( geoInfo.normal ) {
+				return bindMatrices;
 
-					var data = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.normal );
+			},
+			// Parse ambient color in FBXTree.GlobalSettings - if it's not set to black (default), create an ambient light
+			createAmbientLight: function () {
 
-					faceNormals.push( data[ 0 ], data[ 1 ], data[ 2 ] );
+				if ( 'GlobalSettings' in fbxTree && 'AmbientColor' in fbxTree.GlobalSettings ) {
 
-				}
+					var ambientColor = fbxTree.GlobalSettings.AmbientColor.value;
+					var r = ambientColor[ 0 ];
+					var g = ambientColor[ 1 ];
+					var b = ambientColor[ 2 ];
 
-				if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) {
+					if ( r !== 0 || g !== 0 || b !== 0 ) {
 
-					var materialIndex = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.material )[ 0 ];
+						var color = new THREE.Color( r, g, b );
+						sceneGraph.add( new THREE.AmbientLight( color, 1 ) );
 
-				}
+					}
 
-				if ( geoInfo.uv ) {
+				}
 
-					geoInfo.uv.forEach( function ( uv, i ) {
+			},
+			setupMorphMaterials: function () {
 
-						var data = getData( polygonVertexIndex, polygonIndex, vertexIndex, uv );
+				var scope = this;
+				sceneGraph.traverse( function ( child ) {
 
-						if ( faceUVs[ i ] === undefined ) {
+					if ( child.isMesh ) {
 
-							faceUVs[ i ] = [];
+						if ( child.geometry.morphAttributes.position && child.geometry.morphAttributes.position.length ) {
 
-						}
+							if ( Array.isArray( child.material ) ) {
 
-						faceUVs[ i ].push( data[ 0 ] );
-						faceUVs[ i ].push( data[ 1 ] );
+								child.material.forEach( function ( material, i ) {
 
-					} );
+									scope.setupMorphMaterial( child, material, i );
 
-				}
+								} );
 
-				faceLength ++;
+							} else {
 
-				if ( endOfFace ) {
+								scope.setupMorphMaterial( child, child.material );
 
-					scope.genFace( buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength );
+							}
 
-					polygonIndex ++;
-					faceLength = 0;
+						}
 
-					// reset arrays for the next face
-					facePositionIndexes = [];
-					faceNormals = [];
-					faceColors = [];
-					faceUVs = [];
-					faceWeights = [];
-					faceWeightIndices = [];
+					}
 
-				}
+				} );
 
-			} );
+			},
+			setupMorphMaterial: function ( child, material, index ) {
 
-			return buffers;
+				var uuid = child.uuid;
+				var matUuid = material.uuid; // if a geometry has morph targets, it cannot share the material with other geometries
 
-		},
+				var sharedMat = false;
+				sceneGraph.traverse( function ( node ) {
 
-		// Generate data for a single face in a geometry. If the face is a quad then split it into 2 tris
-		genFace: function ( buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength ) {
+					if ( node.isMesh ) {
 
-			for ( var i = 2; i < faceLength; i ++ ) {
+						if ( Array.isArray( node.material ) ) {
 
-				buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 0 ] ] );
-				buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 1 ] ] );
-				buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 2 ] ] );
+							node.material.forEach( function ( mat ) {
 
-				buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 ] ] );
-				buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 + 1 ] ] );
-				buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 + 2 ] ] );
+								if ( mat.uuid === matUuid && node.uuid !== uuid ) sharedMat = true;
 
-				buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 ] ] );
-				buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 + 1 ] ] );
-				buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 + 2 ] ] );
+							} );
 
-				if ( geoInfo.skeleton ) {
+						} else if ( node.material.uuid === matUuid && node.uuid !== uuid ) sharedMat = true;
 
-					buffers.vertexWeights.push( faceWeights[ 0 ] );
-					buffers.vertexWeights.push( faceWeights[ 1 ] );
-					buffers.vertexWeights.push( faceWeights[ 2 ] );
-					buffers.vertexWeights.push( faceWeights[ 3 ] );
+					}
 
-					buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 ] );
-					buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 1 ] );
-					buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 2 ] );
-					buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 3 ] );
+				} );
 
-					buffers.vertexWeights.push( faceWeights[ i * 4 ] );
-					buffers.vertexWeights.push( faceWeights[ i * 4 + 1 ] );
-					buffers.vertexWeights.push( faceWeights[ i * 4 + 2 ] );
-					buffers.vertexWeights.push( faceWeights[ i * 4 + 3 ] );
+				if ( sharedMat === true ) {
 
-					buffers.weightsIndices.push( faceWeightIndices[ 0 ] );
-					buffers.weightsIndices.push( faceWeightIndices[ 1 ] );
-					buffers.weightsIndices.push( faceWeightIndices[ 2 ] );
-					buffers.weightsIndices.push( faceWeightIndices[ 3 ] );
+					var clonedMat = material.clone();
+					clonedMat.morphTargets = true;
+					if ( index === undefined ) child.material = clonedMat; else child.material[ index ] = clonedMat;
 
-					buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 ] );
-					buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 1 ] );
-					buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 2 ] );
-					buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 3 ] );
+				} else material.morphTargets = true;
 
-					buffers.weightsIndices.push( faceWeightIndices[ i * 4 ] );
-					buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 1 ] );
-					buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 2 ] );
-					buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 3 ] );
+			}
+		}; // parse Geometry data from FBXTree and return map of BufferGeometries
 
-				}
+		function GeometryParser() {}
 
-				if ( geoInfo.color ) {
+		GeometryParser.prototype = {
+			constructor: GeometryParser,
+			// Parse nodes in FBXTree.Objects.Geometry
+			parse: function ( deformers ) {
 
-					buffers.colors.push( faceColors[ 0 ] );
-					buffers.colors.push( faceColors[ 1 ] );
-					buffers.colors.push( faceColors[ 2 ] );
+				var geometryMap = new Map();
 
-					buffers.colors.push( faceColors[ ( i - 1 ) * 3 ] );
-					buffers.colors.push( faceColors[ ( i - 1 ) * 3 + 1 ] );
-					buffers.colors.push( faceColors[ ( i - 1 ) * 3 + 2 ] );
+				if ( 'Geometry' in fbxTree.Objects ) {
 
-					buffers.colors.push( faceColors[ i * 3 ] );
-					buffers.colors.push( faceColors[ i * 3 + 1 ] );
-					buffers.colors.push( faceColors[ i * 3 + 2 ] );
+					var geoNodes = fbxTree.Objects.Geometry;
 
-				}
+					for ( var nodeID in geoNodes ) {
 
-				if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) {
+						var relationships = connections.get( parseInt( nodeID ) );
+						var geo = this.parseGeometry( relationships, geoNodes[ nodeID ], deformers );
+						geometryMap.set( parseInt( nodeID ), geo );
 
-					buffers.materialIndex.push( materialIndex );
-					buffers.materialIndex.push( materialIndex );
-					buffers.materialIndex.push( materialIndex );
+					}
 
 				}
 
-				if ( geoInfo.normal ) {
+				return geometryMap;
 
-					buffers.normal.push( faceNormals[ 0 ] );
-					buffers.normal.push( faceNormals[ 1 ] );
-					buffers.normal.push( faceNormals[ 2 ] );
+			},
+			// Parse single node in FBXTree.Objects.Geometry
+			parseGeometry: function ( relationships, geoNode, deformers ) {
 
-					buffers.normal.push( faceNormals[ ( i - 1 ) * 3 ] );
-					buffers.normal.push( faceNormals[ ( i - 1 ) * 3 + 1 ] );
-					buffers.normal.push( faceNormals[ ( i - 1 ) * 3 + 2 ] );
+				switch ( geoNode.attrType ) {
 
-					buffers.normal.push( faceNormals[ i * 3 ] );
-					buffers.normal.push( faceNormals[ i * 3 + 1 ] );
-					buffers.normal.push( faceNormals[ i * 3 + 2 ] );
+					case 'Mesh':
+						return this.parseMeshGeometry( relationships, geoNode, deformers );
+						break;
 
-				}
+					case 'NurbsCurve':
+						return this.parseNurbsGeometry( geoNode );
+						break;
 
-				if ( geoInfo.uv ) {
+				}
 
-					geoInfo.uv.forEach( function ( uv, j ) {
+			},
+			// Parse single node mesh geometry in FBXTree.Objects.Geometry
+			parseMeshGeometry: function ( relationships, geoNode, deformers ) {
 
-						if ( buffers.uvs[ j ] === undefined ) buffers.uvs[ j ] = [];
+				var skeletons = deformers.skeletons;
+				var morphTargets = [];
+				var modelNodes = relationships.parents.map( function ( parent ) {
 
-						buffers.uvs[ j ].push( faceUVs[ j ][ 0 ] );
-						buffers.uvs[ j ].push( faceUVs[ j ][ 1 ] );
+					return fbxTree.Objects.Model[ parent.ID ];
 
-						buffers.uvs[ j ].push( faceUVs[ j ][ ( i - 1 ) * 2 ] );
-						buffers.uvs[ j ].push( faceUVs[ j ][ ( i - 1 ) * 2 + 1 ] );
+				} ); // don't create geometry if it is not associated with any models
 
-						buffers.uvs[ j ].push( faceUVs[ j ][ i * 2 ] );
-						buffers.uvs[ j ].push( faceUVs[ j ][ i * 2 + 1 ] );
+				if ( modelNodes.length === 0 ) return;
+				var skeleton = relationships.children.reduce( function ( skeleton, child ) {
 
-					} );
+					if ( skeletons[ child.ID ] !== undefined ) skeleton = skeletons[ child.ID ];
+					return skeleton;
 
-				}
+				}, null );
+				relationships.children.forEach( function ( child ) {
 
-			}
+					if ( deformers.morphTargets[ child.ID ] !== undefined ) {
 
-		},
+						morphTargets.push( deformers.morphTargets[ child.ID ] );
 
-		addMorphTargets: function ( parentGeo, parentGeoNode, morphTargets, preTransform ) {
+					}
 
-			if ( morphTargets.length === 0 ) return;
+				} ); // Assume one model and get the preRotation from that
+				// if there is more than one model associated with the geometry this may cause problems
 
-			parentGeo.morphTargetsRelative = true;
+				var modelNode = modelNodes[ 0 ];
+				var transformData = {};
+				if ( 'RotationOrder' in modelNode ) transformData.eulerOrder = getEulerOrder( modelNode.RotationOrder.value );
+				if ( 'InheritType' in modelNode ) transformData.inheritType = parseInt( modelNode.InheritType.value );
+				if ( 'GeometricTranslation' in modelNode ) transformData.translation = modelNode.GeometricTranslation.value;
+				if ( 'GeometricRotation' in modelNode ) transformData.rotation = modelNode.GeometricRotation.value;
+				if ( 'GeometricScaling' in modelNode ) transformData.scale = modelNode.GeometricScaling.value;
+				var transform = generateTransform( transformData );
+				return this.genGeometry( geoNode, skeleton, morphTargets, transform );
 
-			parentGeo.morphAttributes.position = [];
-			// parentGeo.morphAttributes.normal = []; // not implemented
+			},
+			// Generate a THREE.BufferGeometry from a node in FBXTree.Objects.Geometry
+			genGeometry: function ( geoNode, skeleton, morphTargets, preTransform ) {
 
-			var scope = this;
-			morphTargets.forEach( function ( morphTarget ) {
+				var geo = new THREE.BufferGeometry();
+				if ( geoNode.attrName ) geo.name = geoNode.attrName;
+				var geoInfo = this.parseGeoNode( geoNode, skeleton );
+				var buffers = this.genBuffers( geoInfo );
+				var positionAttribute = new THREE.Float32BufferAttribute( buffers.vertex, 3 );
+				positionAttribute.applyMatrix4( preTransform );
+				geo.setAttribute( 'position', positionAttribute );
 
-				morphTarget.rawTargets.forEach( function ( rawTarget ) {
+				if ( buffers.colors.length > 0 ) {
 
-					var morphGeoNode = fbxTree.Objects.Geometry[ rawTarget.geoID ];
+					geo.setAttribute( 'color', new THREE.Float32BufferAttribute( buffers.colors, 3 ) );
 
-					if ( morphGeoNode !== undefined ) {
+				}
 
-						scope.genMorphGeometry( parentGeo, parentGeoNode, morphGeoNode, preTransform, rawTarget.name );
+				if ( skeleton ) {
 
-					}
+					geo.setAttribute( 'skinIndex', new THREE.Uint16BufferAttribute( buffers.weightsIndices, 4 ) );
+					geo.setAttribute( 'skinWeight', new THREE.Float32BufferAttribute( buffers.vertexWeights, 4 ) ); // used later to bind the skeleton to the model
 
-				} );
+					geo.FBX_Deformer = skeleton;
 
-			} );
+				}
 
-		},
+				if ( buffers.normal.length > 0 ) {
 
-		// a morph geometry node is similar to a standard  node, and the node is also contained
-		// in FBXTree.Objects.Geometry, however it can only have attributes for position, normal
-		// and a special attribute Index defining which vertices of the original geometry are affected
-		// Normal and position attributes only have data for the vertices that are affected by the morph
-		genMorphGeometry: function ( parentGeo, parentGeoNode, morphGeoNode, preTransform, name ) {
+					var normalMatrix = new THREE.Matrix3().getNormalMatrix( preTransform );
+					var normalAttribute = new THREE.Float32BufferAttribute( buffers.normal, 3 );
+					normalAttribute.applyNormalMatrix( normalMatrix );
+					geo.setAttribute( 'normal', normalAttribute );
 
-			var vertexIndices = ( parentGeoNode.PolygonVertexIndex !== undefined ) ? parentGeoNode.PolygonVertexIndex.a : [];
+				}
 
-			var morphPositionsSparse = ( morphGeoNode.Vertices !== undefined ) ? morphGeoNode.Vertices.a : [];
-			var indices = ( morphGeoNode.Indexes !== undefined ) ? morphGeoNode.Indexes.a : [];
+				buffers.uvs.forEach( function ( uvBuffer, i ) {
 
-			var length = parentGeo.attributes.position.count * 3;
-			var morphPositions = new Float32Array( length );
+					// subsequent uv buffers are called 'uv1', 'uv2', ...
+					var name = 'uv' + ( i + 1 ).toString(); // the first uv buffer is just called 'uv'
 
-			for ( var i = 0; i < indices.length; i ++ ) {
+					if ( i === 0 ) {
 
-				var morphIndex = indices[ i ] * 3;
+						name = 'uv';
 
-				morphPositions[ morphIndex ] = morphPositionsSparse[ i * 3 ];
-				morphPositions[ morphIndex + 1 ] = morphPositionsSparse[ i * 3 + 1 ];
-				morphPositions[ morphIndex + 2 ] = morphPositionsSparse[ i * 3 + 2 ];
+					}
 
-			}
+					geo.setAttribute( name, new THREE.Float32BufferAttribute( buffers.uvs[ i ], 2 ) );
 
-			// TODO: add morph normal support
-			var morphGeoInfo = {
-				vertexIndices: vertexIndices,
-				vertexPositions: morphPositions,
+				} );
 
-			};
+				if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) {
 
-			var morphBuffers = this.genBuffers( morphGeoInfo );
+					// Convert the material indices of each vertex into rendering groups on the geometry.
+					var prevMaterialIndex = buffers.materialIndex[ 0 ];
+					var startIndex = 0;
+					buffers.materialIndex.forEach( function ( currentIndex, i ) {
 
-			var positionAttribute = new THREE.Float32BufferAttribute( morphBuffers.vertex, 3 );
-			positionAttribute.name = name || morphGeoNode.attrName;
+						if ( currentIndex !== prevMaterialIndex ) {
 
-			positionAttribute.applyMatrix4( preTransform );
+							geo.addGroup( startIndex, i - startIndex, prevMaterialIndex );
+							prevMaterialIndex = currentIndex;
+							startIndex = i;
 
-			parentGeo.morphAttributes.position.push( positionAttribute );
+						}
 
-		},
+					} ); // the loop above doesn't add the last group, do that here.
 
-		// Parse normal from FBXTree.Objects.Geometry.LayerElementNormal if it exists
-		parseNormals: function ( NormalNode ) {
+					if ( geo.groups.length > 0 ) {
 
-			var mappingType = NormalNode.MappingInformationType;
-			var referenceType = NormalNode.ReferenceInformationType;
-			var buffer = NormalNode.Normals.a;
-			var indexBuffer = [];
-			if ( referenceType === 'IndexToDirect' ) {
+						var lastGroup = geo.groups[ geo.groups.length - 1 ];
+						var lastIndex = lastGroup.start + lastGroup.count;
 
-				if ( 'NormalIndex' in NormalNode ) {
+						if ( lastIndex !== buffers.materialIndex.length ) {
 
-					indexBuffer = NormalNode.NormalIndex.a;
+							geo.addGroup( lastIndex, buffers.materialIndex.length - lastIndex, prevMaterialIndex );
 
-				} else if ( 'NormalsIndex' in NormalNode ) {
+						}
 
-					indexBuffer = NormalNode.NormalsIndex.a;
+					} // case where there are multiple materials but the whole geometry is only
+					// using one of them
 
-				}
 
-			}
+					if ( geo.groups.length === 0 ) {
 
-			return {
-				dataSize: 3,
-				buffer: buffer,
-				indices: indexBuffer,
-				mappingType: mappingType,
-				referenceType: referenceType
-			};
+						geo.addGroup( 0, buffers.materialIndex.length, buffers.materialIndex[ 0 ] );
 
-		},
+					}
 
-		// Parse UVs from FBXTree.Objects.Geometry.LayerElementUV if it exists
-		parseUVs: function ( UVNode ) {
+				}
 
-			var mappingType = UVNode.MappingInformationType;
-			var referenceType = UVNode.ReferenceInformationType;
-			var buffer = UVNode.UV.a;
-			var indexBuffer = [];
-			if ( referenceType === 'IndexToDirect' ) {
+				this.addMorphTargets( geo, geoNode, morphTargets, preTransform );
+				return geo;
 
-				indexBuffer = UVNode.UVIndex.a;
+			},
+			parseGeoNode: function ( geoNode, skeleton ) {
 
-			}
+				var geoInfo = {};
+				geoInfo.vertexPositions = geoNode.Vertices !== undefined ? geoNode.Vertices.a : [];
+				geoInfo.vertexIndices = geoNode.PolygonVertexIndex !== undefined ? geoNode.PolygonVertexIndex.a : [];
 
-			return {
-				dataSize: 2,
-				buffer: buffer,
-				indices: indexBuffer,
-				mappingType: mappingType,
-				referenceType: referenceType
-			};
+				if ( geoNode.LayerElementColor ) {
 
-		},
+					geoInfo.color = this.parseVertexColors( geoNode.LayerElementColor[ 0 ] );
 
-		// Parse Vertex Colors from FBXTree.Objects.Geometry.LayerElementColor if it exists
-		parseVertexColors: function ( ColorNode ) {
+				}
 
-			var mappingType = ColorNode.MappingInformationType;
-			var referenceType = ColorNode.ReferenceInformationType;
-			var buffer = ColorNode.Colors.a;
-			var indexBuffer = [];
-			if ( referenceType === 'IndexToDirect' ) {
+				if ( geoNode.LayerElementMaterial ) {
 
-				indexBuffer = ColorNode.ColorIndex.a;
+					geoInfo.material = this.parseMaterialIndices( geoNode.LayerElementMaterial[ 0 ] );
 
-			}
+				}
 
-			return {
-				dataSize: 4,
-				buffer: buffer,
-				indices: indexBuffer,
-				mappingType: mappingType,
-				referenceType: referenceType
-			};
+				if ( geoNode.LayerElementNormal ) {
 
-		},
+					geoInfo.normal = this.parseNormals( geoNode.LayerElementNormal[ 0 ] );
 
-		// Parse mapping and material data in FBXTree.Objects.Geometry.LayerElementMaterial if it exists
-		parseMaterialIndices: function ( MaterialNode ) {
+				}
 
-			var mappingType = MaterialNode.MappingInformationType;
-			var referenceType = MaterialNode.ReferenceInformationType;
+				if ( geoNode.LayerElementUV ) {
 
-			if ( mappingType === 'NoMappingInformation' ) {
+					geoInfo.uv = [];
+					var i = 0;
 
-				return {
-					dataSize: 1,
-					buffer: [ 0 ],
-					indices: [ 0 ],
-					mappingType: 'AllSame',
-					referenceType: referenceType
-				};
+					while ( geoNode.LayerElementUV[ i ] ) {
 
-			}
+						if ( geoNode.LayerElementUV[ i ].UV ) {
 
-			var materialIndexBuffer = MaterialNode.Materials.a;
+							geoInfo.uv.push( this.parseUVs( geoNode.LayerElementUV[ i ] ) );
 
-			// Since materials are stored as indices, there's a bit of a mismatch between FBX and what
-			// we expect.So we create an intermediate buffer that points to the index in the buffer,
-			// for conforming with the other functions we've written for other data.
-			var materialIndices = [];
+						}
 
-			for ( var i = 0; i < materialIndexBuffer.length; ++ i ) {
+						i ++;
 
-				materialIndices.push( i );
+					}
 
-			}
+				}
 
-			return {
-				dataSize: 1,
-				buffer: materialIndexBuffer,
-				indices: materialIndices,
-				mappingType: mappingType,
-				referenceType: referenceType
-			};
+				geoInfo.weightTable = {};
 
-		},
+				if ( skeleton !== null ) {
 
-		// Generate a NurbGeometry from a node in FBXTree.Objects.Geometry
-		parseNurbsGeometry: function ( geoNode ) {
+					geoInfo.skeleton = skeleton;
+					skeleton.rawBones.forEach( function ( rawBone, i ) {
 
-			if ( THREE.NURBSCurve === undefined ) {
+						// loop over the bone's vertex indices and weights
+						rawBone.indices.forEach( function ( index, j ) {
 
-				console.error( 'THREE.FBXLoader: The loader relies on THREE.NURBSCurve for any nurbs present in the model. Nurbs will show up as empty geometry.' );
-				return new THREE.BufferGeometry();
+							if ( geoInfo.weightTable[ index ] === undefined ) geoInfo.weightTable[ index ] = [];
+							geoInfo.weightTable[ index ].push( {
+								id: i,
+								weight: rawBone.weights[ j ]
+							} );
 
-			}
+						} );
 
-			var order = parseInt( geoNode.Order );
+					} );
 
-			if ( isNaN( order ) ) {
+				}
 
-				console.error( 'THREE.FBXLoader: Invalid Order %s given for geometry ID: %s', geoNode.Order, geoNode.id );
-				return new THREE.BufferGeometry();
+				return geoInfo;
 
-			}
+			},
+			genBuffers: function ( geoInfo ) {
 
-			var degree = order - 1;
+				var buffers = {
+					vertex: [],
+					normal: [],
+					colors: [],
+					uvs: [],
+					materialIndex: [],
+					vertexWeights: [],
+					weightsIndices: []
+				};
+				var polygonIndex = 0;
+				var faceLength = 0;
+				var displayedWeightsWarning = false; // these will hold data for a single face
 
-			var knots = geoNode.KnotVector.a;
-			var controlPoints = [];
-			var pointsValues = geoNode.Points.a;
+				var facePositionIndexes = [];
+				var faceNormals = [];
+				var faceColors = [];
+				var faceUVs = [];
+				var faceWeights = [];
+				var faceWeightIndices = [];
+				var scope = this;
+				geoInfo.vertexIndices.forEach( function ( vertexIndex, polygonVertexIndex ) {
 
-			for ( var i = 0, l = pointsValues.length; i < l; i += 4 ) {
+					var endOfFace = false; // Face index and vertex index arrays are combined in a single array
+					// A cube with quad faces looks like this:
+					// PolygonVertexIndex: *24 {
+					//	a: 0, 1, 3, -3, 2, 3, 5, -5, 4, 5, 7, -7, 6, 7, 1, -1, 1, 7, 5, -4, 6, 0, 2, -5
+					//	}
+					// Negative numbers mark the end of a face - first face here is 0, 1, 3, -3
+					// to find index of last vertex bit shift the index: ^ - 1
 
-				controlPoints.push( new THREE.Vector4().fromArray( pointsValues, i ) );
+					if ( vertexIndex < 0 ) {
 
-			}
+						vertexIndex = vertexIndex ^ - 1; // equivalent to ( x * -1 ) - 1
 
-			var startKnot, endKnot;
+						endOfFace = true;
 
-			if ( geoNode.Form === 'Closed' ) {
+					}
 
-				controlPoints.push( controlPoints[ 0 ] );
+					var weightIndices = [];
+					var weights = [];
+					facePositionIndexes.push( vertexIndex * 3, vertexIndex * 3 + 1, vertexIndex * 3 + 2 );
 
-			} else if ( geoNode.Form === 'Periodic' ) {
+					if ( geoInfo.color ) {
 
-				startKnot = degree;
-				endKnot = knots.length - 1 - startKnot;
+						var data = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.color );
+						faceColors.push( data[ 0 ], data[ 1 ], data[ 2 ] );
 
-				for ( var i = 0; i < degree; ++ i ) {
+					}
 
-					controlPoints.push( controlPoints[ i ] );
+					if ( geoInfo.skeleton ) {
 
-				}
+						if ( geoInfo.weightTable[ vertexIndex ] !== undefined ) {
 
-			}
+							geoInfo.weightTable[ vertexIndex ].forEach( function ( wt ) {
 
-			var curve = new THREE.NURBSCurve( degree, knots, controlPoints, startKnot, endKnot );
-			var vertices = curve.getPoints( controlPoints.length * 7 );
+								weights.push( wt.weight );
+								weightIndices.push( wt.id );
 
-			var positions = new Float32Array( vertices.length * 3 );
+							} );
 
-			vertices.forEach( function ( vertex, i ) {
+						}
 
-				vertex.toArray( positions, i * 3 );
+						if ( weights.length > 4 ) {
 
-			} );
+							if ( ! displayedWeightsWarning ) {
 
-			var geometry = new THREE.BufferGeometry();
-			geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
+								console.warn( 'THREE.FBXLoader: Vertex has more than 4 skinning weights assigned to vertex. Deleting additional weights.' );
+								displayedWeightsWarning = true;
 
-			return geometry;
+							}
 
-		},
+							var wIndex = [ 0, 0, 0, 0 ];
+							var Weight = [ 0, 0, 0, 0 ];
+							weights.forEach( function ( weight, weightIndex ) {
 
-	};
+								var currentWeight = weight;
+								var currentIndex = weightIndices[ weightIndex ];
+								Weight.forEach( function ( comparedWeight, comparedWeightIndex, comparedWeightArray ) {
 
-	// parse animation data from FBXTree
-	function AnimationParser() {}
+									if ( currentWeight > comparedWeight ) {
 
-	AnimationParser.prototype = {
+										comparedWeightArray[ comparedWeightIndex ] = currentWeight;
+										currentWeight = comparedWeight;
+										var tmp = wIndex[ comparedWeightIndex ];
+										wIndex[ comparedWeightIndex ] = currentIndex;
+										currentIndex = tmp;
 
-		constructor: AnimationParser,
+									}
 
-		// take raw animation clips and turn them into three.js animation clips
-		parse: function () {
+								} );
 
-			var animationClips = [];
+							} );
+							weightIndices = wIndex;
+							weights = Weight;
 
-			var rawClips = this.parseClips();
+						} // if the weight array is shorter than 4 pad with 0s
 
-			if ( rawClips !== undefined ) {
 
-				for ( var key in rawClips ) {
+						while ( weights.length < 4 ) {
 
-					var rawClip = rawClips[ key ];
+							weights.push( 0 );
+							weightIndices.push( 0 );
 
-					var clip = this.addClip( rawClip );
+						}
 
-					animationClips.push( clip );
+						for ( var i = 0; i < 4; ++ i ) {
 
-				}
+							faceWeights.push( weights[ i ] );
+							faceWeightIndices.push( weightIndices[ i ] );
 
-			}
+						}
 
-			return animationClips;
+					}
 
-		},
+					if ( geoInfo.normal ) {
 
-		parseClips: function () {
+						var data = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.normal );
+						faceNormals.push( data[ 0 ], data[ 1 ], data[ 2 ] );
 
-			// since the actual transformation data is stored in FBXTree.Objects.AnimationCurve,
-			// if this is undefined we can safely assume there are no animations
-			if ( fbxTree.Objects.AnimationCurve === undefined ) return undefined;
+					}
 
-			var curveNodesMap = this.parseAnimationCurveNodes();
+					if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) {
 
-			this.parseAnimationCurves( curveNodesMap );
+						var materialIndex = getData( polygonVertexIndex, polygonIndex, vertexIndex, geoInfo.material )[ 0 ];
 
-			var layersMap = this.parseAnimationLayers( curveNodesMap );
-			var rawClips = this.parseAnimStacks( layersMap );
+					}
 
-			return rawClips;
+					if ( geoInfo.uv ) {
 
-		},
+						geoInfo.uv.forEach( function ( uv, i ) {
 
-		// parse nodes in FBXTree.Objects.AnimationCurveNode
-		// each AnimationCurveNode holds data for an animation transform for a model (e.g. left arm rotation )
-		// and is referenced by an AnimationLayer
-		parseAnimationCurveNodes: function () {
+							var data = getData( polygonVertexIndex, polygonIndex, vertexIndex, uv );
 
-			var rawCurveNodes = fbxTree.Objects.AnimationCurveNode;
+							if ( faceUVs[ i ] === undefined ) {
 
-			var curveNodesMap = new Map();
+								faceUVs[ i ] = [];
 
-			for ( var nodeID in rawCurveNodes ) {
+							}
 
-				var rawCurveNode = rawCurveNodes[ nodeID ];
+							faceUVs[ i ].push( data[ 0 ] );
+							faceUVs[ i ].push( data[ 1 ] );
 
-				if ( rawCurveNode.attrName.match( /S|R|T|DeformPercent/ ) !== null ) {
+						} );
 
-					var curveNode = {
+					}
 
-						id: rawCurveNode.id,
-						attr: rawCurveNode.attrName,
-						curves: {},
+					faceLength ++;
 
-					};
+					if ( endOfFace ) {
 
-					curveNodesMap.set( curveNode.id, curveNode );
+						scope.genFace( buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength );
+						polygonIndex ++;
+						faceLength = 0; // reset arrays for the next face
 
-				}
+						facePositionIndexes = [];
+						faceNormals = [];
+						faceColors = [];
+						faceUVs = [];
+						faceWeights = [];
+						faceWeightIndices = [];
 
-			}
+					}
 
-			return curveNodesMap;
+				} );
+				return buffers;
+
+			},
+			// Generate data for a single face in a geometry. If the face is a quad then split it into 2 tris
+			genFace: function ( buffers, geoInfo, facePositionIndexes, materialIndex, faceNormals, faceColors, faceUVs, faceWeights, faceWeightIndices, faceLength ) {
+
+				for ( var i = 2; i < faceLength; i ++ ) {
+
+					buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 0 ] ] );
+					buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 1 ] ] );
+					buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ 2 ] ] );
+					buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 ] ] );
+					buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 + 1 ] ] );
+					buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ ( i - 1 ) * 3 + 2 ] ] );
+					buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 ] ] );
+					buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 + 1 ] ] );
+					buffers.vertex.push( geoInfo.vertexPositions[ facePositionIndexes[ i * 3 + 2 ] ] );
+
+					if ( geoInfo.skeleton ) {
+
+						buffers.vertexWeights.push( faceWeights[ 0 ] );
+						buffers.vertexWeights.push( faceWeights[ 1 ] );
+						buffers.vertexWeights.push( faceWeights[ 2 ] );
+						buffers.vertexWeights.push( faceWeights[ 3 ] );
+						buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 ] );
+						buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 1 ] );
+						buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 2 ] );
+						buffers.vertexWeights.push( faceWeights[ ( i - 1 ) * 4 + 3 ] );
+						buffers.vertexWeights.push( faceWeights[ i * 4 ] );
+						buffers.vertexWeights.push( faceWeights[ i * 4 + 1 ] );
+						buffers.vertexWeights.push( faceWeights[ i * 4 + 2 ] );
+						buffers.vertexWeights.push( faceWeights[ i * 4 + 3 ] );
+						buffers.weightsIndices.push( faceWeightIndices[ 0 ] );
+						buffers.weightsIndices.push( faceWeightIndices[ 1 ] );
+						buffers.weightsIndices.push( faceWeightIndices[ 2 ] );
+						buffers.weightsIndices.push( faceWeightIndices[ 3 ] );
+						buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 ] );
+						buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 1 ] );
+						buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 2 ] );
+						buffers.weightsIndices.push( faceWeightIndices[ ( i - 1 ) * 4 + 3 ] );
+						buffers.weightsIndices.push( faceWeightIndices[ i * 4 ] );
+						buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 1 ] );
+						buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 2 ] );
+						buffers.weightsIndices.push( faceWeightIndices[ i * 4 + 3 ] );
 
-		},
+					}
 
-		// parse nodes in FBXTree.Objects.AnimationCurve and connect them up to
-		// previously parsed AnimationCurveNodes. Each AnimationCurve holds data for a single animated
-		// axis ( e.g. times and values of x rotation)
-		parseAnimationCurves: function ( curveNodesMap ) {
+					if ( geoInfo.color ) {
 
-			var rawCurves = fbxTree.Objects.AnimationCurve;
+						buffers.colors.push( faceColors[ 0 ] );
+						buffers.colors.push( faceColors[ 1 ] );
+						buffers.colors.push( faceColors[ 2 ] );
+						buffers.colors.push( faceColors[ ( i - 1 ) * 3 ] );
+						buffers.colors.push( faceColors[ ( i - 1 ) * 3 + 1 ] );
+						buffers.colors.push( faceColors[ ( i - 1 ) * 3 + 2 ] );
+						buffers.colors.push( faceColors[ i * 3 ] );
+						buffers.colors.push( faceColors[ i * 3 + 1 ] );
+						buffers.colors.push( faceColors[ i * 3 + 2 ] );
 
-			// TODO: Many values are identical up to roundoff error, but won't be optimised
-			// e.g. position times: [0, 0.4, 0. 8]
-			// position values: [7.23538335023477e-7, 93.67518615722656, -0.9982695579528809, 7.23538335023477e-7, 93.67518615722656, -0.9982695579528809, 7.235384487103147e-7, 93.67520904541016, -0.9982695579528809]
-			// clearly, this should be optimised to
-			// times: [0], positions [7.23538335023477e-7, 93.67518615722656, -0.9982695579528809]
-			// this shows up in nearly every FBX file, and generally time array is length > 100
+					}
 
-			for ( var nodeID in rawCurves ) {
+					if ( geoInfo.material && geoInfo.material.mappingType !== 'AllSame' ) {
 
-				var animationCurve = {
+						buffers.materialIndex.push( materialIndex );
+						buffers.materialIndex.push( materialIndex );
+						buffers.materialIndex.push( materialIndex );
 
-					id: rawCurves[ nodeID ].id,
-					times: rawCurves[ nodeID ].KeyTime.a.map( convertFBXTimeToSeconds ),
-					values: rawCurves[ nodeID ].KeyValueFloat.a,
+					}
 
-				};
+					if ( geoInfo.normal ) {
 
-				var relationships = connections.get( animationCurve.id );
+						buffers.normal.push( faceNormals[ 0 ] );
+						buffers.normal.push( faceNormals[ 1 ] );
+						buffers.normal.push( faceNormals[ 2 ] );
+						buffers.normal.push( faceNormals[ ( i - 1 ) * 3 ] );
+						buffers.normal.push( faceNormals[ ( i - 1 ) * 3 + 1 ] );
+						buffers.normal.push( faceNormals[ ( i - 1 ) * 3 + 2 ] );
+						buffers.normal.push( faceNormals[ i * 3 ] );
+						buffers.normal.push( faceNormals[ i * 3 + 1 ] );
+						buffers.normal.push( faceNormals[ i * 3 + 2 ] );
 
-				if ( relationships !== undefined ) {
+					}
 
-					var animationCurveID = relationships.parents[ 0 ].ID;
-					var animationCurveRelationship = relationships.parents[ 0 ].relationship;
+					if ( geoInfo.uv ) {
 
-					if ( animationCurveRelationship.match( /X/ ) ) {
+						geoInfo.uv.forEach( function ( uv, j ) {
 
-						curveNodesMap.get( animationCurveID ).curves[ 'x' ] = animationCurve;
+							if ( buffers.uvs[ j ] === undefined ) buffers.uvs[ j ] = [];
+							buffers.uvs[ j ].push( faceUVs[ j ][ 0 ] );
+							buffers.uvs[ j ].push( faceUVs[ j ][ 1 ] );
+							buffers.uvs[ j ].push( faceUVs[ j ][ ( i - 1 ) * 2 ] );
+							buffers.uvs[ j ].push( faceUVs[ j ][ ( i - 1 ) * 2 + 1 ] );
+							buffers.uvs[ j ].push( faceUVs[ j ][ i * 2 ] );
+							buffers.uvs[ j ].push( faceUVs[ j ][ i * 2 + 1 ] );
 
-					} else if ( animationCurveRelationship.match( /Y/ ) ) {
+						} );
 
-						curveNodesMap.get( animationCurveID ).curves[ 'y' ] = animationCurve;
+					}
 
-					} else if ( animationCurveRelationship.match( /Z/ ) ) {
+				}
 
-						curveNodesMap.get( animationCurveID ).curves[ 'z' ] = animationCurve;
+			},
+			addMorphTargets: function ( parentGeo, parentGeoNode, morphTargets, preTransform ) {
 
-					} else if ( animationCurveRelationship.match( /d|DeformPercent/ ) && curveNodesMap.has( animationCurveID ) ) {
+				if ( morphTargets.length === 0 ) return;
+				parentGeo.morphTargetsRelative = true;
+				parentGeo.morphAttributes.position = []; // parentGeo.morphAttributes.normal = []; // not implemented
 
-						curveNodesMap.get( animationCurveID ).curves[ 'morph' ] = animationCurve;
+				var scope = this;
+				morphTargets.forEach( function ( morphTarget ) {
 
-					}
+					morphTarget.rawTargets.forEach( function ( rawTarget ) {
 
-				}
+						var morphGeoNode = fbxTree.Objects.Geometry[ rawTarget.geoID ];
 
-			}
+						if ( morphGeoNode !== undefined ) {
 
-		},
+							scope.genMorphGeometry( parentGeo, parentGeoNode, morphGeoNode, preTransform, rawTarget.name );
 
-		// parse nodes in FBXTree.Objects.AnimationLayer. Each layers holds references
-		// to various AnimationCurveNodes and is referenced by an AnimationStack node
-		// note: theoretically a stack can have multiple layers, however in practice there always seems to be one per stack
-		parseAnimationLayers: function ( curveNodesMap ) {
+						}
 
-			var rawLayers = fbxTree.Objects.AnimationLayer;
+					} );
 
-			var layersMap = new Map();
+				} );
 
-			for ( var nodeID in rawLayers ) {
+			},
+			// a morph geometry node is similar to a standard	node, and the node is also contained
+			// in FBXTree.Objects.Geometry, however it can only have attributes for position, normal
+			// and a special attribute Index defining which vertices of the original geometry are affected
+			// Normal and position attributes only have data for the vertices that are affected by the morph
+			genMorphGeometry: function ( parentGeo, parentGeoNode, morphGeoNode, preTransform, name ) {
 
-				var layerCurveNodes = [];
+				var vertexIndices = parentGeoNode.PolygonVertexIndex !== undefined ? parentGeoNode.PolygonVertexIndex.a : [];
+				var morphPositionsSparse = morphGeoNode.Vertices !== undefined ? morphGeoNode.Vertices.a : [];
+				var indices = morphGeoNode.Indexes !== undefined ? morphGeoNode.Indexes.a : [];
+				var length = parentGeo.attributes.position.count * 3;
+				var morphPositions = new Float32Array( length );
 
-				var connection = connections.get( parseInt( nodeID ) );
+				for ( var i = 0; i < indices.length; i ++ ) {
 
-				if ( connection !== undefined ) {
+					var morphIndex = indices[ i ] * 3;
+					morphPositions[ morphIndex ] = morphPositionsSparse[ i * 3 ];
+					morphPositions[ morphIndex + 1 ] = morphPositionsSparse[ i * 3 + 1 ];
+					morphPositions[ morphIndex + 2 ] = morphPositionsSparse[ i * 3 + 2 ];
 
-					// all the animationCurveNodes used in the layer
-					var children = connection.children;
+				} // TODO: add morph normal support
 
-					children.forEach( function ( child, i ) {
 
-						if ( curveNodesMap.has( child.ID ) ) {
+				var morphGeoInfo = {
+					vertexIndices: vertexIndices,
+					vertexPositions: morphPositions
+				};
+				var morphBuffers = this.genBuffers( morphGeoInfo );
+				var positionAttribute = new THREE.Float32BufferAttribute( morphBuffers.vertex, 3 );
+				positionAttribute.name = name || morphGeoNode.attrName;
+				positionAttribute.applyMatrix4( preTransform );
+				parentGeo.morphAttributes.position.push( positionAttribute );
 
-							var curveNode = curveNodesMap.get( child.ID );
+			},
+			// Parse normal from FBXTree.Objects.Geometry.LayerElementNormal if it exists
+			parseNormals: function ( NormalNode ) {
 
-							// check that the curves are defined for at least one axis, otherwise ignore the curveNode
-							if ( curveNode.curves.x !== undefined || curveNode.curves.y !== undefined || curveNode.curves.z !== undefined ) {
+				var mappingType = NormalNode.MappingInformationType;
+				var referenceType = NormalNode.ReferenceInformationType;
+				var buffer = NormalNode.Normals.a;
+				var indexBuffer = [];
 
-								if ( layerCurveNodes[ i ] === undefined ) {
+				if ( referenceType === 'IndexToDirect' ) {
 
-									var modelID = connections.get( child.ID ).parents.filter( function ( parent ) {
+					if ( 'NormalIndex' in NormalNode ) {
 
-										return parent.relationship !== undefined;
+						indexBuffer = NormalNode.NormalIndex.a;
 
-									} )[ 0 ].ID;
+					} else if ( 'NormalsIndex' in NormalNode ) {
 
-									if ( modelID !== undefined ) {
+						indexBuffer = NormalNode.NormalsIndex.a;
 
-										var rawModel = fbxTree.Objects.Model[ modelID.toString() ];
+					}
 
-										if ( rawModel === undefined ) {
+				}
 
-											console.warn( 'THREE.FBXLoader: Encountered a unused curve.', child );
-											return;
+				return {
+					dataSize: 3,
+					buffer: buffer,
+					indices: indexBuffer,
+					mappingType: mappingType,
+					referenceType: referenceType
+				};
 
-										}
+			},
+			// Parse UVs from FBXTree.Objects.Geometry.LayerElementUV if it exists
+			parseUVs: function ( UVNode ) {
 
-										var node = {
+				var mappingType = UVNode.MappingInformationType;
+				var referenceType = UVNode.ReferenceInformationType;
+				var buffer = UVNode.UV.a;
+				var indexBuffer = [];
 
-											modelName: rawModel.attrName ? THREE.PropertyBinding.sanitizeNodeName( rawModel.attrName ) : '',
-											ID: rawModel.id,
-											initialPosition: [ 0, 0, 0 ],
-											initialRotation: [ 0, 0, 0 ],
-											initialScale: [ 1, 1, 1 ],
+				if ( referenceType === 'IndexToDirect' ) {
 
-										};
+					indexBuffer = UVNode.UVIndex.a;
 
-										sceneGraph.traverse( function ( child ) {
+				}
 
-											if ( child.ID === rawModel.id ) {
+				return {
+					dataSize: 2,
+					buffer: buffer,
+					indices: indexBuffer,
+					mappingType: mappingType,
+					referenceType: referenceType
+				};
 
-												node.transform = child.matrix;
+			},
+			// Parse Vertex Colors from FBXTree.Objects.Geometry.LayerElementColor if it exists
+			parseVertexColors: function ( ColorNode ) {
 
-												if ( child.userData.transformData ) node.eulerOrder = child.userData.transformData.eulerOrder;
+				var mappingType = ColorNode.MappingInformationType;
+				var referenceType = ColorNode.ReferenceInformationType;
+				var buffer = ColorNode.Colors.a;
+				var indexBuffer = [];
 
-											}
+				if ( referenceType === 'IndexToDirect' ) {
 
-										} );
+					indexBuffer = ColorNode.ColorIndex.a;
 
-										if ( ! node.transform ) node.transform = new THREE.Matrix4();
+				}
 
-										// if the animated model is pre rotated, we'll have to apply the pre rotations to every
-										// animation value as well
-										if ( 'PreRotation' in rawModel ) node.preRotation = rawModel.PreRotation.value;
-										if ( 'PostRotation' in rawModel ) node.postRotation = rawModel.PostRotation.value;
+				return {
+					dataSize: 4,
+					buffer: buffer,
+					indices: indexBuffer,
+					mappingType: mappingType,
+					referenceType: referenceType
+				};
 
-										layerCurveNodes[ i ] = node;
+			},
+			// Parse mapping and material data in FBXTree.Objects.Geometry.LayerElementMaterial if it exists
+			parseMaterialIndices: function ( MaterialNode ) {
 
-									}
+				var mappingType = MaterialNode.MappingInformationType;
+				var referenceType = MaterialNode.ReferenceInformationType;
 
-								}
+				if ( mappingType === 'NoMappingInformation' ) {
 
-								if ( layerCurveNodes[ i ] ) layerCurveNodes[ i ][ curveNode.attr ] = curveNode;
+					return {
+						dataSize: 1,
+						buffer: [ 0 ],
+						indices: [ 0 ],
+						mappingType: 'AllSame',
+						referenceType: referenceType
+					};
 
-							} else if ( curveNode.curves.morph !== undefined ) {
+				}
 
-								if ( layerCurveNodes[ i ] === undefined ) {
+				var materialIndexBuffer = MaterialNode.Materials.a; // Since materials are stored as indices, there's a bit of a mismatch between FBX and what
+				// we expect.So we create an intermediate buffer that points to the index in the buffer,
+				// for conforming with the other functions we've written for other data.
 
-									var deformerID = connections.get( child.ID ).parents.filter( function ( parent ) {
+				var materialIndices = [];
 
-										return parent.relationship !== undefined;
+				for ( var i = 0; i < materialIndexBuffer.length; ++ i ) {
 
-									} )[ 0 ].ID;
+					materialIndices.push( i );
 
-									var morpherID = connections.get( deformerID ).parents[ 0 ].ID;
-									var geoID = connections.get( morpherID ).parents[ 0 ].ID;
+				}
 
-									// assuming geometry is not used in more than one model
-									var modelID = connections.get( geoID ).parents[ 0 ].ID;
+				return {
+					dataSize: 1,
+					buffer: materialIndexBuffer,
+					indices: materialIndices,
+					mappingType: mappingType,
+					referenceType: referenceType
+				};
 
-									var rawModel = fbxTree.Objects.Model[ modelID ];
+			},
+			// Generate a NurbGeometry from a node in FBXTree.Objects.Geometry
+			parseNurbsGeometry: function ( geoNode ) {
 
-									var node = {
+				if ( THREE.NURBSCurve === undefined ) {
 
-										modelName: rawModel.attrName ? THREE.PropertyBinding.sanitizeNodeName( rawModel.attrName ) : '',
-										morphName: fbxTree.Objects.Deformer[ deformerID ].attrName,
+					console.error( 'THREE.FBXLoader: The loader relies on THREE.NURBSCurve for any nurbs present in the model. Nurbs will show up as empty geometry.' );
+					return new THREE.BufferGeometry();
 
-									};
+				}
 
-									layerCurveNodes[ i ] = node;
+				var order = parseInt( geoNode.Order );
 
-								}
+				if ( isNaN( order ) ) {
 
-								layerCurveNodes[ i ][ curveNode.attr ] = curveNode;
+					console.error( 'THREE.FBXLoader: Invalid Order %s given for geometry ID: %s', geoNode.Order, geoNode.id );
+					return new THREE.BufferGeometry();
 
-							}
+				}
 
-						}
+				var degree = order - 1;
+				var knots = geoNode.KnotVector.a;
+				var controlPoints = [];
+				var pointsValues = geoNode.Points.a;
 
-					} );
+				for ( var i = 0, l = pointsValues.length; i < l; i += 4 ) {
 
-					layersMap.set( parseInt( nodeID ), layerCurveNodes );
+					controlPoints.push( new THREE.Vector4().fromArray( pointsValues, i ) );
 
 				}
 
-			}
-
-			return layersMap;
-
-		},
+				var startKnot, endKnot;
 
-		// parse nodes in FBXTree.Objects.AnimationStack. These are the top level node in the animation
-		// hierarchy. Each Stack node will be used to create a THREE.AnimationClip
-		parseAnimStacks: function ( layersMap ) {
+				if ( geoNode.Form === 'Closed' ) {
 
-			var rawStacks = fbxTree.Objects.AnimationStack;
+					controlPoints.push( controlPoints[ 0 ] );
 
-			// connect the stacks (clips) up to the layers
-			var rawClips = {};
+				} else if ( geoNode.Form === 'Periodic' ) {
 
-			for ( var nodeID in rawStacks ) {
+					startKnot = degree;
+					endKnot = knots.length - 1 - startKnot;
 
-				var children = connections.get( parseInt( nodeID ) ).children;
+					for ( var i = 0; i < degree; ++ i ) {
 
-				if ( children.length > 1 ) {
+						controlPoints.push( controlPoints[ i ] );
 
-					// it seems like stacks will always be associated with a single layer. But just in case there are files
-					// where there are multiple layers per stack, we'll display a warning
-					console.warn( 'THREE.FBXLoader: Encountered an animation stack with multiple layers, this is currently not supported. Ignoring subsequent layers.' );
+					}
 
 				}
 
-				var layer = layersMap.get( children[ 0 ].ID );
-
-				rawClips[ nodeID ] = {
+				var curve = new THREE.NURBSCurve( degree, knots, controlPoints, startKnot, endKnot );
+				var vertices = curve.getPoints( controlPoints.length * 7 );
+				var positions = new Float32Array( vertices.length * 3 );
+				vertices.forEach( function ( vertex, i ) {
 
-					name: rawStacks[ nodeID ].attrName,
-					layer: layer,
+					vertex.toArray( positions, i * 3 );
 
-				};
+				} );
+				var geometry = new THREE.BufferGeometry();
+				geometry.setAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
+				return geometry;
 
 			}
+		}; // parse animation data from FBXTree
 
-			return rawClips;
-
-		},
-
-		addClip: function ( rawClip ) {
+		function AnimationParser() {}
 
-			var tracks = [];
+		AnimationParser.prototype = {
+			constructor: AnimationParser,
+			// take raw animation clips and turn them into three.js animation clips
+			parse: function () {
 
-			var scope = this;
-			rawClip.layer.forEach( function ( rawTracks ) {
+				var animationClips = [];
+				var rawClips = this.parseClips();
 
-				tracks = tracks.concat( scope.generateTracks( rawTracks ) );
+				if ( rawClips !== undefined ) {
 
-			} );
-
-			return new THREE.AnimationClip( rawClip.name, - 1, tracks );
+					for ( var key in rawClips ) {
 
-		},
+						var rawClip = rawClips[ key ];
+						var clip = this.addClip( rawClip );
+						animationClips.push( clip );
 
-		generateTracks: function ( rawTracks ) {
+					}
 
-			var tracks = [];
+				}
 
-			var initialPosition = new THREE.Vector3();
-			var initialRotation = new THREE.Quaternion();
-			var initialScale = new THREE.Vector3();
+				return animationClips;
 
-			if ( rawTracks.transform ) rawTracks.transform.decompose( initialPosition, initialRotation, initialScale );
+			},
+			parseClips: function () {
 
-			initialPosition = initialPosition.toArray();
-			initialRotation = new THREE.Euler().setFromQuaternion( initialRotation, rawTracks.eulerOrder ).toArray();
-			initialScale = initialScale.toArray();
+				// since the actual transformation data is stored in FBXTree.Objects.AnimationCurve,
+				// if this is undefined we can safely assume there are no animations
+				if ( fbxTree.Objects.AnimationCurve === undefined ) return undefined;
+				var curveNodesMap = this.parseAnimationCurveNodes();
+				this.parseAnimationCurves( curveNodesMap );
+				var layersMap = this.parseAnimationLayers( curveNodesMap );
+				var rawClips = this.parseAnimStacks( layersMap );
+				return rawClips;
 
-			if ( rawTracks.T !== undefined && Object.keys( rawTracks.T.curves ).length > 0 ) {
+			},
+			// parse nodes in FBXTree.Objects.AnimationCurveNode
+			// each AnimationCurveNode holds data for an animation transform for a model (e.g. left arm rotation )
+			// and is referenced by an AnimationLayer
+			parseAnimationCurveNodes: function () {
 
-				var positionTrack = this.generateVectorTrack( rawTracks.modelName, rawTracks.T.curves, initialPosition, 'position' );
-				if ( positionTrack !== undefined ) tracks.push( positionTrack );
+				var rawCurveNodes = fbxTree.Objects.AnimationCurveNode;
+				var curveNodesMap = new Map();
 
-			}
+				for ( var nodeID in rawCurveNodes ) {
 
-			if ( rawTracks.R !== undefined && Object.keys( rawTracks.R.curves ).length > 0 ) {
+					var rawCurveNode = rawCurveNodes[ nodeID ];
 
-				var rotationTrack = this.generateRotationTrack( rawTracks.modelName, rawTracks.R.curves, initialRotation, rawTracks.preRotation, rawTracks.postRotation, rawTracks.eulerOrder );
-				if ( rotationTrack !== undefined ) tracks.push( rotationTrack );
+					if ( rawCurveNode.attrName.match( /S|R|T|DeformPercent/ ) !== null ) {
 
-			}
+						var curveNode = {
+							id: rawCurveNode.id,
+							attr: rawCurveNode.attrName,
+							curves: {}
+						};
+						curveNodesMap.set( curveNode.id, curveNode );
 
-			if ( rawTracks.S !== undefined && Object.keys( rawTracks.S.curves ).length > 0 ) {
+					}
 
-				var scaleTrack = this.generateVectorTrack( rawTracks.modelName, rawTracks.S.curves, initialScale, 'scale' );
-				if ( scaleTrack !== undefined ) tracks.push( scaleTrack );
+				}
 
-			}
+				return curveNodesMap;
 
-			if ( rawTracks.DeformPercent !== undefined ) {
+			},
+			// parse nodes in FBXTree.Objects.AnimationCurve and connect them up to
+			// previously parsed AnimationCurveNodes. Each AnimationCurve holds data for a single animated
+			// axis ( e.g. times and values of x rotation)
+			parseAnimationCurves: function ( curveNodesMap ) {
 
-				var morphTrack = this.generateMorphTrack( rawTracks );
-				if ( morphTrack !== undefined ) tracks.push( morphTrack );
+				var rawCurves = fbxTree.Objects.AnimationCurve; // TODO: Many values are identical up to roundoff error, but won't be optimised
+				// e.g. position times: [0, 0.4, 0. 8]
+				// position values: [7.23538335023477e-7, 93.67518615722656, -0.9982695579528809, 7.23538335023477e-7, 93.67518615722656, -0.9982695579528809, 7.235384487103147e-7, 93.67520904541016, -0.9982695579528809]
+				// clearly, this should be optimised to
+				// times: [0], positions [7.23538335023477e-7, 93.67518615722656, -0.9982695579528809]
+				// this shows up in nearly every FBX file, and generally time array is length > 100
 
-			}
+				for ( var nodeID in rawCurves ) {
 
-			return tracks;
+					var animationCurve = {
+						id: rawCurves[ nodeID ].id,
+						times: rawCurves[ nodeID ].KeyTime.a.map( convertFBXTimeToSeconds ),
+						values: rawCurves[ nodeID ].KeyValueFloat.a
+					};
+					var relationships = connections.get( animationCurve.id );
 
-		},
+					if ( relationships !== undefined ) {
 
-		generateVectorTrack: function ( modelName, curves, initialValue, type ) {
+						var animationCurveID = relationships.parents[ 0 ].ID;
+						var animationCurveRelationship = relationships.parents[ 0 ].relationship;
 
-			var times = this.getTimesForAllAxes( curves );
-			var values = this.getKeyframeTrackValues( times, curves, initialValue );
+						if ( animationCurveRelationship.match( /X/ ) ) {
 
-			return new THREE.VectorKeyframeTrack( modelName + '.' + type, times, values );
+							curveNodesMap.get( animationCurveID ).curves[ 'x' ] = animationCurve;
 
-		},
+						} else if ( animationCurveRelationship.match( /Y/ ) ) {
 
-		generateRotationTrack: function ( modelName, curves, initialValue, preRotation, postRotation, eulerOrder ) {
+							curveNodesMap.get( animationCurveID ).curves[ 'y' ] = animationCurve;
 
-			if ( curves.x !== undefined ) {
+						} else if ( animationCurveRelationship.match( /Z/ ) ) {
 
-				this.interpolateRotations( curves.x );
-				curves.x.values = curves.x.values.map( THREE.MathUtils.degToRad );
+							curveNodesMap.get( animationCurveID ).curves[ 'z' ] = animationCurve;
 
-			}
+						} else if ( animationCurveRelationship.match( /d|DeformPercent/ ) && curveNodesMap.has( animationCurveID ) ) {
 
-			if ( curves.y !== undefined ) {
+							curveNodesMap.get( animationCurveID ).curves[ 'morph' ] = animationCurve;
 
-				this.interpolateRotations( curves.y );
-				curves.y.values = curves.y.values.map( THREE.MathUtils.degToRad );
+						}
 
-			}
+					}
 
-			if ( curves.z !== undefined ) {
+				}
 
-				this.interpolateRotations( curves.z );
-				curves.z.values = curves.z.values.map( THREE.MathUtils.degToRad );
+			},
+			// parse nodes in FBXTree.Objects.AnimationLayer. Each layers holds references
+			// to various AnimationCurveNodes and is referenced by an AnimationStack node
+			// note: theoretically a stack can have multiple layers, however in practice there always seems to be one per stack
+			parseAnimationLayers: function ( curveNodesMap ) {
 
-			}
+				var rawLayers = fbxTree.Objects.AnimationLayer;
+				var layersMap = new Map();
 
-			var times = this.getTimesForAllAxes( curves );
-			var values = this.getKeyframeTrackValues( times, curves, initialValue );
+				for ( var nodeID in rawLayers ) {
 
-			if ( preRotation !== undefined ) {
+					var layerCurveNodes = [];
+					var connection = connections.get( parseInt( nodeID ) );
 
-				preRotation = preRotation.map( THREE.MathUtils.degToRad );
-				preRotation.push( eulerOrder );
+					if ( connection !== undefined ) {
 
-				preRotation = new THREE.Euler().fromArray( preRotation );
-				preRotation = new THREE.Quaternion().setFromEuler( preRotation );
+						// all the animationCurveNodes used in the layer
+						var children = connection.children;
+						children.forEach( function ( child, i ) {
 
-			}
+							if ( curveNodesMap.has( child.ID ) ) {
 
-			if ( postRotation !== undefined ) {
+								var curveNode = curveNodesMap.get( child.ID ); // check that the curves are defined for at least one axis, otherwise ignore the curveNode
 
-				postRotation = postRotation.map( THREE.MathUtils.degToRad );
-				postRotation.push( eulerOrder );
+								if ( curveNode.curves.x !== undefined || curveNode.curves.y !== undefined || curveNode.curves.z !== undefined ) {
 
-				postRotation = new THREE.Euler().fromArray( postRotation );
-				postRotation = new THREE.Quaternion().setFromEuler( postRotation ).invert();
+									if ( layerCurveNodes[ i ] === undefined ) {
 
-			}
+										var modelID = connections.get( child.ID ).parents.filter( function ( parent ) {
 
-			var quaternion = new THREE.Quaternion();
-			var euler = new THREE.Euler();
+											return parent.relationship !== undefined;
 
-			var quaternionValues = [];
+										} )[ 0 ].ID;
 
-			for ( var i = 0; i < values.length; i += 3 ) {
+										if ( modelID !== undefined ) {
 
-				euler.set( values[ i ], values[ i + 1 ], values[ i + 2 ], eulerOrder );
+											var rawModel = fbxTree.Objects.Model[ modelID.toString() ];
 
-				quaternion.setFromEuler( euler );
+											if ( rawModel === undefined ) {
 
-				if ( preRotation !== undefined ) quaternion.premultiply( preRotation );
-				if ( postRotation !== undefined ) quaternion.multiply( postRotation );
+												console.warn( 'THREE.FBXLoader: Encountered a unused curve.', child );
+												return;
 
-				quaternion.toArray( quaternionValues, ( i / 3 ) * 4 );
+											}
 
-			}
+											var node = {
+												modelName: rawModel.attrName ? THREE.PropertyBinding.sanitizeNodeName( rawModel.attrName ) : '',
+												ID: rawModel.id,
+												initialPosition: [ 0, 0, 0 ],
+												initialRotation: [ 0, 0, 0 ],
+												initialScale: [ 1, 1, 1 ]
+											};
+											sceneGraph.traverse( function ( child ) {
 
-			return new THREE.QuaternionKeyframeTrack( modelName + '.quaternion', times, quaternionValues );
+												if ( child.ID === rawModel.id ) {
 
-		},
+													node.transform = child.matrix;
+													if ( child.userData.transformData ) node.eulerOrder = child.userData.transformData.eulerOrder;
 
-		generateMorphTrack: function ( rawTracks ) {
+												}
 
-			var curves = rawTracks.DeformPercent.curves.morph;
-			var values = curves.values.map( function ( val ) {
+											} );
+											if ( ! node.transform ) node.transform = new THREE.Matrix4(); // if the animated model is pre rotated, we'll have to apply the pre rotations to every
+											// animation value as well
 
-				return val / 100;
+											if ( 'PreRotation' in rawModel ) node.preRotation = rawModel.PreRotation.value;
+											if ( 'PostRotation' in rawModel ) node.postRotation = rawModel.PostRotation.value;
+											layerCurveNodes[ i ] = node;
 
-			} );
+										}
 
-			var morphNum = sceneGraph.getObjectByName( rawTracks.modelName ).morphTargetDictionary[ rawTracks.morphName ];
+									}
 
-			return new THREE.NumberKeyframeTrack( rawTracks.modelName + '.morphTargetInfluences[' + morphNum + ']', curves.times, values );
+									if ( layerCurveNodes[ i ] ) layerCurveNodes[ i ][ curveNode.attr ] = curveNode;
 
-		},
+								} else if ( curveNode.curves.morph !== undefined ) {
 
-		// For all animated objects, times are defined separately for each axis
-		// Here we'll combine the times into one sorted array without duplicates
-		getTimesForAllAxes: function ( curves ) {
+									if ( layerCurveNodes[ i ] === undefined ) {
 
-			var times = [];
+										var deformerID = connections.get( child.ID ).parents.filter( function ( parent ) {
 
-			// first join together the times for each axis, if defined
-			if ( curves.x !== undefined ) times = times.concat( curves.x.times );
-			if ( curves.y !== undefined ) times = times.concat( curves.y.times );
-			if ( curves.z !== undefined ) times = times.concat( curves.z.times );
+											return parent.relationship !== undefined;
 
-			// then sort them
-			times = times.sort( function ( a, b ) {
+										} )[ 0 ].ID;
+										var morpherID = connections.get( deformerID ).parents[ 0 ].ID;
+										var geoID = connections.get( morpherID ).parents[ 0 ].ID; // assuming geometry is not used in more than one model
 
-				return a - b;
+										var modelID = connections.get( geoID ).parents[ 0 ].ID;
+										var rawModel = fbxTree.Objects.Model[ modelID ];
+										var node = {
+											modelName: rawModel.attrName ? THREE.PropertyBinding.sanitizeNodeName( rawModel.attrName ) : '',
+											morphName: fbxTree.Objects.Deformer[ deformerID ].attrName
+										};
+										layerCurveNodes[ i ] = node;
 
-			} );
+									}
 
-			// and remove duplicates
-			if ( times.length > 1 ) {
+									layerCurveNodes[ i ][ curveNode.attr ] = curveNode;
 
-				var targetIndex = 1;
-				var lastValue = times[ 0 ];
-				for ( var i = 1; i < times.length; i ++ ) {
+								}
 
-					var currentValue = times[ i ];
-					if ( currentValue !== lastValue ) {
+							}
 
-						times[ targetIndex ] = currentValue;
-						lastValue = currentValue;
-						targetIndex ++;
+						} );
+						layersMap.set( parseInt( nodeID ), layerCurveNodes );
 
 					}
 
 				}
 
-				times = times.slice( 0, targetIndex );
-
-			}
-
-			return times;
-
-		},
+				return layersMap;
 
-		getKeyframeTrackValues: function ( times, curves, initialValue ) {
+			},
+			// parse nodes in FBXTree.Objects.AnimationStack. These are the top level node in the animation
+			// hierarchy. Each Stack node will be used to create a THREE.AnimationClip
+			parseAnimStacks: function ( layersMap ) {
 
-			var prevValue = initialValue;
+				var rawStacks = fbxTree.Objects.AnimationStack; // connect the stacks (clips) up to the layers
 
-			var values = [];
+				var rawClips = {};
 
-			var xIndex = - 1;
-			var yIndex = - 1;
-			var zIndex = - 1;
+				for ( var nodeID in rawStacks ) {
 
-			times.forEach( function ( time ) {
+					var children = connections.get( parseInt( nodeID ) ).children;
 
-				if ( curves.x ) xIndex = curves.x.times.indexOf( time );
-				if ( curves.y ) yIndex = curves.y.times.indexOf( time );
-				if ( curves.z ) zIndex = curves.z.times.indexOf( time );
+					if ( children.length > 1 ) {
 
-				// if there is an x value defined for this frame, use that
-				if ( xIndex !== - 1 ) {
+						// it seems like stacks will always be associated with a single layer. But just in case there are files
+						// where there are multiple layers per stack, we'll display a warning
+						console.warn( 'THREE.FBXLoader: Encountered an animation stack with multiple layers, this is currently not supported. Ignoring subsequent layers.' );
 
-					var xValue = curves.x.values[ xIndex ];
-					values.push( xValue );
-					prevValue[ 0 ] = xValue;
-
-				} else {
+					}
 
-					// otherwise use the x value from the previous frame
-					values.push( prevValue[ 0 ] );
+					var layer = layersMap.get( children[ 0 ].ID );
+					rawClips[ nodeID ] = {
+						name: rawStacks[ nodeID ].attrName,
+						layer: layer
+					};
 
 				}
 
-				if ( yIndex !== - 1 ) {
+				return rawClips;
 
-					var yValue = curves.y.values[ yIndex ];
-					values.push( yValue );
-					prevValue[ 1 ] = yValue;
+			},
+			addClip: function ( rawClip ) {
 
-				} else {
+				var tracks = [];
+				var scope = this;
+				rawClip.layer.forEach( function ( rawTracks ) {
 
-					values.push( prevValue[ 1 ] );
+					tracks = tracks.concat( scope.generateTracks( rawTracks ) );
 
-				}
+				} );
+				return new THREE.AnimationClip( rawClip.name, - 1, tracks );
 
-				if ( zIndex !== - 1 ) {
+			},
+			generateTracks: function ( rawTracks ) {
 
-					var zValue = curves.z.values[ zIndex ];
-					values.push( zValue );
-					prevValue[ 2 ] = zValue;
+				var tracks = [];
+				var initialPosition = new THREE.Vector3();
+				var initialRotation = new THREE.Quaternion();
+				var initialScale = new THREE.Vector3();
+				if ( rawTracks.transform ) rawTracks.transform.decompose( initialPosition, initialRotation, initialScale );
+				initialPosition = initialPosition.toArray();
+				initialRotation = new THREE.Euler().setFromQuaternion( initialRotation, rawTracks.eulerOrder ).toArray();
+				initialScale = initialScale.toArray();
 
-				} else {
+				if ( rawTracks.T !== undefined && Object.keys( rawTracks.T.curves ).length > 0 ) {
 
-					values.push( prevValue[ 2 ] );
+					var positionTrack = this.generateVectorTrack( rawTracks.modelName, rawTracks.T.curves, initialPosition, 'position' );
+					if ( positionTrack !== undefined ) tracks.push( positionTrack );
 
 				}
 
-			} );
+				if ( rawTracks.R !== undefined && Object.keys( rawTracks.R.curves ).length > 0 ) {
 
-			return values;
+					var rotationTrack = this.generateRotationTrack( rawTracks.modelName, rawTracks.R.curves, initialRotation, rawTracks.preRotation, rawTracks.postRotation, rawTracks.eulerOrder );
+					if ( rotationTrack !== undefined ) tracks.push( rotationTrack );
 
-		},
+				}
 
-		// Rotations are defined as Euler angles which can have values  of any size
-		// These will be converted to quaternions which don't support values greater than
-		// PI, so we'll interpolate large rotations
-		interpolateRotations: function ( curve ) {
+				if ( rawTracks.S !== undefined && Object.keys( rawTracks.S.curves ).length > 0 ) {
 
-			for ( var i = 1; i < curve.values.length; i ++ ) {
+					var scaleTrack = this.generateVectorTrack( rawTracks.modelName, rawTracks.S.curves, initialScale, 'scale' );
+					if ( scaleTrack !== undefined ) tracks.push( scaleTrack );
 
-				var initialValue = curve.values[ i - 1 ];
-				var valuesSpan = curve.values[ i ] - initialValue;
+				}
 
-				var absoluteSpan = Math.abs( valuesSpan );
+				if ( rawTracks.DeformPercent !== undefined ) {
 
-				if ( absoluteSpan >= 180 ) {
+					var morphTrack = this.generateMorphTrack( rawTracks );
+					if ( morphTrack !== undefined ) tracks.push( morphTrack );
 
-					var numSubIntervals = absoluteSpan / 180;
+				}
 
-					var step = valuesSpan / numSubIntervals;
-					var nextValue = initialValue + step;
+				return tracks;
 
-					var initialTime = curve.times[ i - 1 ];
-					var timeSpan = curve.times[ i ] - initialTime;
-					var interval = timeSpan / numSubIntervals;
-					var nextTime = initialTime + interval;
+			},
+			generateVectorTrack: function ( modelName, curves, initialValue, type ) {
 
-					var interpolatedTimes = [];
-					var interpolatedValues = [];
+				var times = this.getTimesForAllAxes( curves );
+				var values = this.getKeyframeTrackValues( times, curves, initialValue );
+				return new THREE.VectorKeyframeTrack( modelName + '.' + type, times, values );
 
-					while ( nextTime < curve.times[ i ] ) {
+			},
+			generateRotationTrack: function ( modelName, curves, initialValue, preRotation, postRotation, eulerOrder ) {
 
-						interpolatedTimes.push( nextTime );
-						nextTime += interval;
+				if ( curves.x !== undefined ) {
 
-						interpolatedValues.push( nextValue );
-						nextValue += step;
+					this.interpolateRotations( curves.x );
+					curves.x.values = curves.x.values.map( THREE.MathUtils.degToRad );
 
-					}
+				}
 
-					curve.times = inject( curve.times, i, interpolatedTimes );
-					curve.values = inject( curve.values, i, interpolatedValues );
+				if ( curves.y !== undefined ) {
 
-				}
+					this.interpolateRotations( curves.y );
+					curves.y.values = curves.y.values.map( THREE.MathUtils.degToRad );
 
-			}
+				}
 
-		},
+				if ( curves.z !== undefined ) {
 
-	};
+					this.interpolateRotations( curves.z );
+					curves.z.values = curves.z.values.map( THREE.MathUtils.degToRad );
 
-	// parse an FBX file in ASCII format
-	function TextParser() {}
+				}
 
-	TextParser.prototype = {
+				var times = this.getTimesForAllAxes( curves );
+				var values = this.getKeyframeTrackValues( times, curves, initialValue );
 
-		constructor: TextParser,
+				if ( preRotation !== undefined ) {
 
-		getPrevNode: function () {
+					preRotation = preRotation.map( THREE.MathUtils.degToRad );
+					preRotation.push( eulerOrder );
+					preRotation = new THREE.Euler().fromArray( preRotation );
+					preRotation = new THREE.Quaternion().setFromEuler( preRotation );
 
-			return this.nodeStack[ this.currentIndent - 2 ];
+				}
 
-		},
+				if ( postRotation !== undefined ) {
 
-		getCurrentNode: function () {
+					postRotation = postRotation.map( THREE.MathUtils.degToRad );
+					postRotation.push( eulerOrder );
+					postRotation = new THREE.Euler().fromArray( postRotation );
+					postRotation = new THREE.Quaternion().setFromEuler( postRotation ).invert();
 
-			return this.nodeStack[ this.currentIndent - 1 ];
+				}
 
-		},
+				var quaternion = new THREE.Quaternion();
+				var euler = new THREE.Euler();
+				var quaternionValues = [];
 
-		getCurrentProp: function () {
+				for ( var i = 0; i < values.length; i += 3 ) {
 
-			return this.currentProp;
+					euler.set( values[ i ], values[ i + 1 ], values[ i + 2 ], eulerOrder );
+					quaternion.setFromEuler( euler );
+					if ( preRotation !== undefined ) quaternion.premultiply( preRotation );
+					if ( postRotation !== undefined ) quaternion.multiply( postRotation );
+					quaternion.toArray( quaternionValues, i / 3 * 4 );
 
-		},
+				}
 
-		pushStack: function ( node ) {
+				return new THREE.QuaternionKeyframeTrack( modelName + '.quaternion', times, quaternionValues );
 
-			this.nodeStack.push( node );
-			this.currentIndent += 1;
+			},
+			generateMorphTrack: function ( rawTracks ) {
 
-		},
+				var curves = rawTracks.DeformPercent.curves.morph;
+				var values = curves.values.map( function ( val ) {
 
-		popStack: function () {
+					return val / 100;
 
-			this.nodeStack.pop();
-			this.currentIndent -= 1;
+				} );
+				var morphNum = sceneGraph.getObjectByName( rawTracks.modelName ).morphTargetDictionary[ rawTracks.morphName ];
+				return new THREE.NumberKeyframeTrack( rawTracks.modelName + '.morphTargetInfluences[' + morphNum + ']', curves.times, values );
 
-		},
+			},
+			// For all animated objects, times are defined separately for each axis
+			// Here we'll combine the times into one sorted array without duplicates
+			getTimesForAllAxes: function ( curves ) {
 
-		setCurrentProp: function ( val, name ) {
+				var times = []; // first join together the times for each axis, if defined
 
-			this.currentProp = val;
-			this.currentPropName = name;
+				if ( curves.x !== undefined ) times = times.concat( curves.x.times );
+				if ( curves.y !== undefined ) times = times.concat( curves.y.times );
+				if ( curves.z !== undefined ) times = times.concat( curves.z.times ); // then sort them
 
-		},
+				times = times.sort( function ( a, b ) {
 
-		parse: function ( text ) {
+					return a - b;
 
-			this.currentIndent = 0;
+				} ); // and remove duplicates
 
-			this.allNodes = new FBXTree();
-			this.nodeStack = [];
-			this.currentProp = [];
-			this.currentPropName = '';
+				if ( times.length > 1 ) {
 
-			var scope = this;
+					var targetIndex = 1;
+					var lastValue = times[ 0 ];
 
-			var split = text.split( /[\r\n]+/ );
+					for ( var i = 1; i < times.length; i ++ ) {
 
-			split.forEach( function ( line, i ) {
+						var currentValue = times[ i ];
 
-				var matchComment = line.match( /^[\s\t]*;/ );
-				var matchEmpty = line.match( /^[\s\t]*$/ );
+						if ( currentValue !== lastValue ) {
 
-				if ( matchComment || matchEmpty ) return;
+							times[ targetIndex ] = currentValue;
+							lastValue = currentValue;
+							targetIndex ++;
 
-				var matchBeginning = line.match( '^\\t{' + scope.currentIndent + '}(\\w+):(.*){', '' );
-				var matchProperty = line.match( '^\\t{' + ( scope.currentIndent ) + '}(\\w+):[\\s\\t\\r\\n](.*)' );
-				var matchEnd = line.match( '^\\t{' + ( scope.currentIndent - 1 ) + '}}' );
+						}
 
-				if ( matchBeginning ) {
+					}
 
-					scope.parseNodeBegin( line, matchBeginning );
+					times = times.slice( 0, targetIndex );
 
-				} else if ( matchProperty ) {
+				}
 
-					scope.parseNodeProperty( line, matchProperty, split[ ++ i ] );
+				return times;
 
-				} else if ( matchEnd ) {
+			},
+			getKeyframeTrackValues: function ( times, curves, initialValue ) {
 
-					scope.popStack();
+				var prevValue = initialValue;
+				var values = [];
+				var xIndex = - 1;
+				var yIndex = - 1;
+				var zIndex = - 1;
+				times.forEach( function ( time ) {
 
-				} else if ( line.match( /^[^\s\t}]/ ) ) {
+					if ( curves.x ) xIndex = curves.x.times.indexOf( time );
+					if ( curves.y ) yIndex = curves.y.times.indexOf( time );
+					if ( curves.z ) zIndex = curves.z.times.indexOf( time ); // if there is an x value defined for this frame, use that
 
-					// large arrays are split over multiple lines terminated with a ',' character
-					// if this is encountered the line needs to be joined to the previous line
-					scope.parseNodePropertyContinued( line );
+					if ( xIndex !== - 1 ) {
 
-				}
+						var xValue = curves.x.values[ xIndex ];
+						values.push( xValue );
+						prevValue[ 0 ] = xValue;
 
-			} );
+					} else {
 
-			return this.allNodes;
+						// otherwise use the x value from the previous frame
+						values.push( prevValue[ 0 ] );
 
-		},
+					}
 
-		parseNodeBegin: function ( line, property ) {
+					if ( yIndex !== - 1 ) {
 
-			var nodeName = property[ 1 ].trim().replace( /^"/, '' ).replace( /"$/, '' );
+						var yValue = curves.y.values[ yIndex ];
+						values.push( yValue );
+						prevValue[ 1 ] = yValue;
 
-			var nodeAttrs = property[ 2 ].split( ',' ).map( function ( attr ) {
+					} else {
 
-				return attr.trim().replace( /^"/, '' ).replace( /"$/, '' );
+						values.push( prevValue[ 1 ] );
 
-			} );
+					}
 
-			var node = { name: nodeName };
-			var attrs = this.parseNodeAttr( nodeAttrs );
+					if ( zIndex !== - 1 ) {
 
-			var currentNode = this.getCurrentNode();
+						var zValue = curves.z.values[ zIndex ];
+						values.push( zValue );
+						prevValue[ 2 ] = zValue;
 
-			// a top node
-			if ( this.currentIndent === 0 ) {
+					} else {
 
-				this.allNodes.add( nodeName, node );
+						values.push( prevValue[ 2 ] );
 
-			} else { // a subnode
+					}
 
-				// if the subnode already exists, append it
-				if ( nodeName in currentNode ) {
+				} );
+				return values;
 
-					// special case Pose needs PoseNodes as an array
-					if ( nodeName === 'PoseNode' ) {
+			},
+			// Rotations are defined as THREE.Euler angles which can have values	of any size
+			// These will be converted to quaternions which don't support values greater than
+			// PI, so we'll interpolate large rotations
+			interpolateRotations: function ( curve ) {
 
-						currentNode.PoseNode.push( node );
+				for ( var i = 1; i < curve.values.length; i ++ ) {
 
-					} else if ( currentNode[ nodeName ].id !== undefined ) {
+					var initialValue = curve.values[ i - 1 ];
+					var valuesSpan = curve.values[ i ] - initialValue;
+					var absoluteSpan = Math.abs( valuesSpan );
 
-						currentNode[ nodeName ] = {};
-						currentNode[ nodeName ][ currentNode[ nodeName ].id ] = currentNode[ nodeName ];
+					if ( absoluteSpan >= 180 ) {
 
-					}
+						var numSubIntervals = absoluteSpan / 180;
+						var step = valuesSpan / numSubIntervals;
+						var nextValue = initialValue + step;
+						var initialTime = curve.times[ i - 1 ];
+						var timeSpan = curve.times[ i ] - initialTime;
+						var interval = timeSpan / numSubIntervals;
+						var nextTime = initialTime + interval;
+						var interpolatedTimes = [];
+						var interpolatedValues = [];
 
-					if ( attrs.id !== '' ) currentNode[ nodeName ][ attrs.id ] = node;
+						while ( nextTime < curve.times[ i ] ) {
 
-				} else if ( typeof attrs.id === 'number' ) {
+							interpolatedTimes.push( nextTime );
+							nextTime += interval;
+							interpolatedValues.push( nextValue );
+							nextValue += step;
 
-					currentNode[ nodeName ] = {};
-					currentNode[ nodeName ][ attrs.id ] = node;
+						}
 
-				} else if ( nodeName !== 'Properties70' ) {
+						curve.times = inject( curve.times, i, interpolatedTimes );
+						curve.values = inject( curve.values, i, interpolatedValues );
 
-					if ( nodeName === 'PoseNode' )	currentNode[ nodeName ] = [ node ];
-					else currentNode[ nodeName ] = node;
+					}
 
 				}
 
 			}
+		}; // parse an FBX file in ASCII format
 
-			if ( typeof attrs.id === 'number' ) node.id = attrs.id;
-			if ( attrs.name !== '' ) node.attrName = attrs.name;
-			if ( attrs.type !== '' ) node.attrType = attrs.type;
-
-			this.pushStack( node );
+		function TextParser() {}
 
-		},
+		TextParser.prototype = {
+			constructor: TextParser,
+			getPrevNode: function () {
 
-		parseNodeAttr: function ( attrs ) {
+				return this.nodeStack[ this.currentIndent - 2 ];
 
-			var id = attrs[ 0 ];
+			},
+			getCurrentNode: function () {
 
-			if ( attrs[ 0 ] !== '' ) {
+				return this.nodeStack[ this.currentIndent - 1 ];
 
-				id = parseInt( attrs[ 0 ] );
+			},
+			getCurrentProp: function () {
 
-				if ( isNaN( id ) ) {
+				return this.currentProp;
 
-					id = attrs[ 0 ];
-
-				}
+			},
+			pushStack: function ( node ) {
 
-			}
+				this.nodeStack.push( node );
+				this.currentIndent += 1;
 
-			var name = '', type = '';
+			},
+			popStack: function () {
 
-			if ( attrs.length > 1 ) {
+				this.nodeStack.pop();
+				this.currentIndent -= 1;
 
-				name = attrs[ 1 ].replace( /^(\w+)::/, '' );
-				type = attrs[ 2 ];
+			},
+			setCurrentProp: function ( val, name ) {
 
-			}
+				this.currentProp = val;
+				this.currentPropName = name;
 
-			return { id: id, name: name, type: type };
+			},
+			parse: function ( text ) {
 
-		},
+				this.currentIndent = 0;
+				this.allNodes = new FBXTree();
+				this.nodeStack = [];
+				this.currentProp = [];
+				this.currentPropName = '';
+				var scope = this;
+				var split = text.split( /[\r\n]+/ );
+				split.forEach( function ( line, i ) {
 
-		parseNodeProperty: function ( line, property, contentLine ) {
+					var matchComment = line.match( /^[\s\t]*;/ );
+					var matchEmpty = line.match( /^[\s\t]*$/ );
+					if ( matchComment || matchEmpty ) return;
+					var matchBeginning = line.match( '^\\t{' + scope.currentIndent + '}(\\w+):(.*){', '' );
+					var matchProperty = line.match( '^\\t{' + scope.currentIndent + '}(\\w+):[\\s\\t\\r\\n](.*)' );
+					var matchEnd = line.match( '^\\t{' + ( scope.currentIndent - 1 ) + '}}' );
 
-			var propName = property[ 1 ].replace( /^"/, '' ).replace( /"$/, '' ).trim();
-			var propValue = property[ 2 ].replace( /^"/, '' ).replace( /"$/, '' ).trim();
+					if ( matchBeginning ) {
 
-			// for special case: base64 image data follows "Content: ," line
-			//	Content: ,
-			//	 "/9j/4RDaRXhpZgAATU0A..."
-			if ( propName === 'Content' && propValue === ',' ) {
+						scope.parseNodeBegin( line, matchBeginning );
 
-				propValue = contentLine.replace( /"/g, '' ).replace( /,$/, '' ).trim();
+					} else if ( matchProperty ) {
 
-			}
+						scope.parseNodeProperty( line, matchProperty, split[ ++ i ] );
 
-			var currentNode = this.getCurrentNode();
-			var parentName = currentNode.name;
+					} else if ( matchEnd ) {
 
-			if ( parentName === 'Properties70' ) {
+						scope.popStack();
 
-				this.parseNodeSpecialProperty( line, propName, propValue );
-				return;
+					} else if ( line.match( /^[^\s\t}]/ ) ) {
 
-			}
+						// large arrays are split over multiple lines terminated with a ',' character
+						// if this is encountered the line needs to be joined to the previous line
+						scope.parseNodePropertyContinued( line );
 
-			// Connections
-			if ( propName === 'C' ) {
+					}
 
-				var connProps = propValue.split( ',' ).slice( 1 );
-				var from = parseInt( connProps[ 0 ] );
-				var to = parseInt( connProps[ 1 ] );
+				} );
+				return this.allNodes;
 
-				var rest = propValue.split( ',' ).slice( 3 );
+			},
+			parseNodeBegin: function ( line, property ) {
 
-				rest = rest.map( function ( elem ) {
+				var nodeName = property[ 1 ].trim().replace( /^"/, '' ).replace( /"$/, '' );
+				var nodeAttrs = property[ 2 ].split( ',' ).map( function ( attr ) {
 
-					return elem.trim().replace( /^"/, '' );
+					return attr.trim().replace( /^"/, '' ).replace( /"$/, '' );
 
 				} );
+				var node = {
+					name: nodeName
+				};
+				var attrs = this.parseNodeAttr( nodeAttrs );
+				var currentNode = this.getCurrentNode(); // a top node
 
-				propName = 'connections';
-				propValue = [ from, to ];
-				append( propValue, rest );
-
-				if ( currentNode[ propName ] === undefined ) {
+				if ( this.currentIndent === 0 ) {
 
-					currentNode[ propName ] = [];
+					this.allNodes.add( nodeName, node );
 
-				}
+				} else {
 
-			}
+					// a subnode
+					// if the subnode already exists, append it
+					if ( nodeName in currentNode ) {
 
-			// Node
-			if ( propName === 'Node' ) currentNode.id = propValue;
+						// special case Pose needs PoseNodes as an array
+						if ( nodeName === 'PoseNode' ) {
 
-			// connections
-			if ( propName in currentNode && Array.isArray( currentNode[ propName ] ) ) {
+							currentNode.PoseNode.push( node );
 
-				currentNode[ propName ].push( propValue );
+						} else if ( currentNode[ nodeName ].id !== undefined ) {
 
-			} else {
+							currentNode[ nodeName ] = {};
+							currentNode[ nodeName ][ currentNode[ nodeName ].id ] = currentNode[ nodeName ];
 
-				if ( propName !== 'a' ) currentNode[ propName ] = propValue;
-				else currentNode.a = propValue;
+						}
 
-			}
+						if ( attrs.id !== '' ) currentNode[ nodeName ][ attrs.id ] = node;
 
-			this.setCurrentProp( currentNode, propName );
+					} else if ( typeof attrs.id === 'number' ) {
 
-			// convert string to array, unless it ends in ',' in which case more will be added to it
-			if ( propName === 'a' && propValue.slice( - 1 ) !== ',' ) {
+						currentNode[ nodeName ] = {};
+						currentNode[ nodeName ][ attrs.id ] = node;
 
-				currentNode.a = parseNumberArray( propValue );
+					} else if ( nodeName !== 'Properties70' ) {
 
-			}
+						if ( nodeName === 'PoseNode' ) currentNode[ nodeName ] = [ node ]; else currentNode[ nodeName ] = node;
 
-		},
+					}
 
-		parseNodePropertyContinued: function ( line ) {
+				}
 
-			var currentNode = this.getCurrentNode();
+				if ( typeof attrs.id === 'number' ) node.id = attrs.id;
+				if ( attrs.name !== '' ) node.attrName = attrs.name;
+				if ( attrs.type !== '' ) node.attrType = attrs.type;
+				this.pushStack( node );
 
-			currentNode.a += line;
+			},
+			parseNodeAttr: function ( attrs ) {
 
-			// if the line doesn't end in ',' we have reached the end of the property value
-			// so convert the string to an array
-			if ( line.slice( - 1 ) !== ',' ) {
+				var id = attrs[ 0 ];
 
-				currentNode.a = parseNumberArray( currentNode.a );
+				if ( attrs[ 0 ] !== '' ) {
 
-			}
+					id = parseInt( attrs[ 0 ] );
 
-		},
+					if ( isNaN( id ) ) {
 
-		// parse "Property70"
-		parseNodeSpecialProperty: function ( line, propName, propValue ) {
+						id = attrs[ 0 ];
 
-			// split this
-			// P: "Lcl Scaling", "Lcl Scaling", "", "A",1,1,1
-			// into array like below
-			// ["Lcl Scaling", "Lcl Scaling", "", "A", "1,1,1" ]
-			var props = propValue.split( '",' ).map( function ( prop ) {
+					}
 
-				return prop.trim().replace( /^\"/, '' ).replace( /\s/, '_' );
+				}
 
-			} );
+				var name = '',
+					type = '';
 
-			var innerPropName = props[ 0 ];
-			var innerPropType1 = props[ 1 ];
-			var innerPropType2 = props[ 2 ];
-			var innerPropFlag = props[ 3 ];
-			var innerPropValue = props[ 4 ];
-
-			// cast values where needed, otherwise leave as strings
-			switch ( innerPropType1 ) {
-
-				case 'int':
-				case 'enum':
-				case 'bool':
-				case 'ULongLong':
-				case 'double':
-				case 'Number':
-				case 'FieldOfView':
-					innerPropValue = parseFloat( innerPropValue );
-					break;
+				if ( attrs.length > 1 ) {
 
-				case 'Color':
-				case 'ColorRGB':
-				case 'Vector3D':
-				case 'Lcl_Translation':
-				case 'Lcl_Rotation':
-				case 'Lcl_Scaling':
-					innerPropValue = parseNumberArray( innerPropValue );
-					break;
+					name = attrs[ 1 ].replace( /^(\w+)::/, '' );
+					type = attrs[ 2 ];
 
-			}
+				}
 
-			// CAUTION: these props must append to parent's parent
-			this.getPrevNode()[ innerPropName ] = {
+				return {
+					id: id,
+					name: name,
+					type: type
+				};
 
-				'type': innerPropType1,
-				'type2': innerPropType2,
-				'flag': innerPropFlag,
-				'value': innerPropValue
+			},
+			parseNodeProperty: function ( line, property, contentLine ) {
 
-			};
+				var propName = property[ 1 ].replace( /^"/, '' ).replace( /"$/, '' ).trim();
+				var propValue = property[ 2 ].replace( /^"/, '' ).replace( /"$/, '' ).trim(); // for special case: base64 image data follows "Content: ," line
+				//	Content: ,
+				//	 "/9j/4RDaRXhpZgAATU0A..."
 
-			this.setCurrentProp( this.getPrevNode(), innerPropName );
+				if ( propName === 'Content' && propValue === ',' ) {
 
-		},
+					propValue = contentLine.replace( /"/g, '' ).replace( /,$/, '' ).trim();
 
-	};
+				}
 
-	// Parse an FBX file in Binary format
-	function BinaryParser() {}
+				var currentNode = this.getCurrentNode();
+				var parentName = currentNode.name;
 
-	BinaryParser.prototype = {
+				if ( parentName === 'Properties70' ) {
 
-		constructor: BinaryParser,
+					this.parseNodeSpecialProperty( line, propName, propValue );
+					return;
 
-		parse: function ( buffer ) {
+				} // Connections
 
-			var reader = new BinaryReader( buffer );
-			reader.skip( 23 ); // skip magic 23 bytes
 
-			var version = reader.getUint32();
+				if ( propName === 'C' ) {
 
-			if ( version < 6400 ) {
+					var connProps = propValue.split( ',' ).slice( 1 );
+					var from = parseInt( connProps[ 0 ] );
+					var to = parseInt( connProps[ 1 ] );
+					var rest = propValue.split( ',' ).slice( 3 );
+					rest = rest.map( function ( elem ) {
 
-				throw new Error( 'THREE.FBXLoader: FBX version not supported, FileVersion: ' + version );
+						return elem.trim().replace( /^"/, '' );
 
-			}
+					} );
+					propName = 'connections';
+					propValue = [ from, to ];
+					append( propValue, rest );
 
-			var allNodes = new FBXTree();
+					if ( currentNode[ propName ] === undefined ) {
 
-			while ( ! this.endOfContent( reader ) ) {
+						currentNode[ propName ] = [];
 
-				var node = this.parseNode( reader, version );
-				if ( node !== null ) allNodes.add( node.name, node );
+					}
 
-			}
+				} // Node
 
-			return allNodes;
 
-		},
+				if ( propName === 'Node' ) currentNode.id = propValue; // connections
 
-		// Check if reader has reached the end of content.
-		endOfContent: function ( reader ) {
+				if ( propName in currentNode && Array.isArray( currentNode[ propName ] ) ) {
 
-			// footer size: 160bytes + 16-byte alignment padding
-			// - 16bytes: magic
-			// - padding til 16-byte alignment (at least 1byte?)
-			//	(seems like some exporters embed fixed 15 or 16bytes?)
-			// - 4bytes: magic
-			// - 4bytes: version
-			// - 120bytes: zero
-			// - 16bytes: magic
-			if ( reader.size() % 16 === 0 ) {
+					currentNode[ propName ].push( propValue );
 
-				return ( ( reader.getOffset() + 160 + 16 ) & ~ 0xf ) >= reader.size();
+				} else {
 
-			} else {
+					if ( propName !== 'a' ) currentNode[ propName ] = propValue; else currentNode.a = propValue;
 
-				return reader.getOffset() + 160 + 16 >= reader.size();
+				}
 
-			}
+				this.setCurrentProp( currentNode, propName ); // convert string to array, unless it ends in ',' in which case more will be added to it
 
-		},
+				if ( propName === 'a' && propValue.slice( - 1 ) !== ',' ) {
 
-		// recursively parse nodes until the end of the file is reached
-		parseNode: function ( reader, version ) {
+					currentNode.a = parseNumberArray( propValue );
 
-			var node = {};
+				}
 
-			// The first three data sizes depends on version.
-			var endOffset = ( version >= 7500 ) ? reader.getUint64() : reader.getUint32();
-			var numProperties = ( version >= 7500 ) ? reader.getUint64() : reader.getUint32();
+			},
+			parseNodePropertyContinued: function ( line ) {
 
-			( version >= 7500 ) ? reader.getUint64() : reader.getUint32(); // the returned propertyListLen is not used
+				var currentNode = this.getCurrentNode();
+				currentNode.a += line; // if the line doesn't end in ',' we have reached the end of the property value
+				// so convert the string to an array
 
-			var nameLen = reader.getUint8();
-			var name = reader.getString( nameLen );
+				if ( line.slice( - 1 ) !== ',' ) {
 
-			// Regards this node as NULL-record if endOffset is zero
-			if ( endOffset === 0 ) return null;
+					currentNode.a = parseNumberArray( currentNode.a );
 
-			var propertyList = [];
+				}
 
-			for ( var i = 0; i < numProperties; i ++ ) {
+			},
+			// parse "Property70"
+			parseNodeSpecialProperty: function ( line, propName, propValue ) {
 
-				propertyList.push( this.parseProperty( reader ) );
+				// split this
+				// P: "Lcl Scaling", "Lcl Scaling", "", "A",1,1,1
+				// into array like below
+				// ["Lcl Scaling", "Lcl Scaling", "", "A", "1,1,1" ]
+				var props = propValue.split( '",' ).map( function ( prop ) {
 
-			}
+					return prop.trim().replace( /^\"/, '' ).replace( /\s/, '_' );
 
-			// Regards the first three elements in propertyList as id, attrName, and attrType
-			var id = propertyList.length > 0 ? propertyList[ 0 ] : '';
-			var attrName = propertyList.length > 1 ? propertyList[ 1 ] : '';
-			var attrType = propertyList.length > 2 ? propertyList[ 2 ] : '';
+				} );
+				var innerPropName = props[ 0 ];
+				var innerPropType1 = props[ 1 ];
+				var innerPropType2 = props[ 2 ];
+				var innerPropFlag = props[ 3 ];
+				var innerPropValue = props[ 4 ]; // cast values where needed, otherwise leave as strings
+
+				switch ( innerPropType1 ) {
+
+					case 'int':
+					case 'enum':
+					case 'bool':
+					case 'ULongLong':
+					case 'double':
+					case 'Number':
+					case 'FieldOfView':
+						innerPropValue = parseFloat( innerPropValue );
+						break;
 
-			// check if this node represents just a single property
-			// like (name, 0) set or (name2, [0, 1, 2]) set of {name: 0, name2: [0, 1, 2]}
-			node.singleProperty = ( numProperties === 1 && reader.getOffset() === endOffset ) ? true : false;
+					case 'Color':
+					case 'ColorRGB':
+					case 'Vector3D':
+					case 'Lcl_Translation':
+					case 'Lcl_Rotation':
+					case 'Lcl_Scaling':
+						innerPropValue = parseNumberArray( innerPropValue );
+						break;
 
-			while ( endOffset > reader.getOffset() ) {
+				} // CAUTION: these props must append to parent's parent
 
-				var subNode = this.parseNode( reader, version );
 
-				if ( subNode !== null ) this.parseSubNode( name, node, subNode );
+				this.getPrevNode()[ innerPropName ] = {
+					'type': innerPropType1,
+					'type2': innerPropType2,
+					'flag': innerPropFlag,
+					'value': innerPropValue
+				};
+				this.setCurrentProp( this.getPrevNode(), innerPropName );
 
 			}
+		}; // Parse an FBX file in Binary format
 
-			node.propertyList = propertyList; // raw property list used by parent
-
-			if ( typeof id === 'number' ) node.id = id;
-			if ( attrName !== '' ) node.attrName = attrName;
-			if ( attrType !== '' ) node.attrType = attrType;
-			if ( name !== '' ) node.name = name;
+		function BinaryParser() {}
 
-			return node;
+		BinaryParser.prototype = {
+			constructor: BinaryParser,
+			parse: function ( buffer ) {
 
-		},
+				var reader = new BinaryReader( buffer );
+				reader.skip( 23 ); // skip magic 23 bytes
 
-		parseSubNode: function ( name, node, subNode ) {
+				var version = reader.getUint32();
 
-			// special case: child node is single property
-			if ( subNode.singleProperty === true ) {
+				if ( version < 6400 ) {
 
-				var value = subNode.propertyList[ 0 ];
+					throw new Error( 'THREE.FBXLoader: FBX version not supported, FileVersion: ' + version );
 
-				if ( Array.isArray( value ) ) {
-
-					node[ subNode.name ] = subNode;
+				}
 
-					subNode.a = value;
+				var allNodes = new FBXTree();
 
-				} else {
+				while ( ! this.endOfContent( reader ) ) {
 
-					node[ subNode.name ] = value;
+					var node = this.parseNode( reader, version );
+					if ( node !== null ) allNodes.add( node.name, node );
 
 				}
 
-			} else if ( name === 'Connections' && subNode.name === 'C' ) {
-
-				var array = [];
+				return allNodes;
 
-				subNode.propertyList.forEach( function ( property, i ) {
+			},
+			// Check if reader has reached the end of content.
+			endOfContent: function ( reader ) {
 
-					// first Connection is FBX type (OO, OP, etc.). We'll discard these
-					if ( i !== 0 ) array.push( property );
+				// footer size: 160bytes + 16-byte alignment padding
+				// - 16bytes: magic
+				// - padding til 16-byte alignment (at least 1byte?)
+				//	(seems like some exporters embed fixed 15 or 16bytes?)
+				// - 4bytes: magic
+				// - 4bytes: version
+				// - 120bytes: zero
+				// - 16bytes: magic
+				if ( reader.size() % 16 === 0 ) {
 
-				} );
+					return ( reader.getOffset() + 160 + 16 & ~ 0xf ) >= reader.size();
 
-				if ( node.connections === undefined ) {
+				} else {
 
-					node.connections = [];
+					return reader.getOffset() + 160 + 16 >= reader.size();
 
 				}
 
-				node.connections.push( array );
+			},
+			// recursively parse nodes until the end of the file is reached
+			parseNode: function ( reader, version ) {
 
-			} else if ( subNode.name === 'Properties70' ) {
+				var node = {}; // The first three data sizes depends on version.
 
-				var keys = Object.keys( subNode );
+				var endOffset = version >= 7500 ? reader.getUint64() : reader.getUint32();
+				var numProperties = version >= 7500 ? reader.getUint64() : reader.getUint32();
+				version >= 7500 ? reader.getUint64() : reader.getUint32(); // the returned propertyListLen is not used
 
-				keys.forEach( function ( key ) {
+				var nameLen = reader.getUint8();
+				var name = reader.getString( nameLen ); // Regards this node as NULL-record if endOffset is zero
 
-					node[ key ] = subNode[ key ];
+				if ( endOffset === 0 ) return null;
+				var propertyList = [];
 
-				} );
+				for ( var i = 0; i < numProperties; i ++ ) {
 
-			} else if ( name === 'Properties70' && subNode.name === 'P' ) {
+					propertyList.push( this.parseProperty( reader ) );
 
-				var innerPropName = subNode.propertyList[ 0 ];
-				var innerPropType1 = subNode.propertyList[ 1 ];
-				var innerPropType2 = subNode.propertyList[ 2 ];
-				var innerPropFlag = subNode.propertyList[ 3 ];
-				var innerPropValue;
+				} // Regards the first three elements in propertyList as id, attrName, and attrType
 
-				if ( innerPropName.indexOf( 'Lcl ' ) === 0 ) innerPropName = innerPropName.replace( 'Lcl ', 'Lcl_' );
-				if ( innerPropType1.indexOf( 'Lcl ' ) === 0 ) innerPropType1 = innerPropType1.replace( 'Lcl ', 'Lcl_' );
 
-				if ( innerPropType1 === 'Color' || innerPropType1 === 'ColorRGB' || innerPropType1 === 'Vector' || innerPropType1 === 'Vector3D' || innerPropType1.indexOf( 'Lcl_' ) === 0 ) {
+				var id = propertyList.length > 0 ? propertyList[ 0 ] : '';
+				var attrName = propertyList.length > 1 ? propertyList[ 1 ] : '';
+				var attrType = propertyList.length > 2 ? propertyList[ 2 ] : ''; // check if this node represents just a single property
+				// like (name, 0) set or (name2, [0, 1, 2]) set of {name: 0, name2: [0, 1, 2]}
 
-					innerPropValue = [
-						subNode.propertyList[ 4 ],
-						subNode.propertyList[ 5 ],
-						subNode.propertyList[ 6 ]
-					];
+				node.singleProperty = numProperties === 1 && reader.getOffset() === endOffset ? true : false;
 
-				} else {
+				while ( endOffset > reader.getOffset() ) {
 
-					innerPropValue = subNode.propertyList[ 4 ];
+					var subNode = this.parseNode( reader, version );
+					if ( subNode !== null ) this.parseSubNode( name, node, subNode );
 
 				}
 
-				// this will be copied to parent, see above
-				node[ innerPropName ] = {
-
-					'type': innerPropType1,
-					'type2': innerPropType2,
-					'flag': innerPropFlag,
-					'value': innerPropValue
-
-				};
-
-			} else if ( node[ subNode.name ] === undefined ) {
+				node.propertyList = propertyList; // raw property list used by parent
 
-				if ( typeof subNode.id === 'number' ) {
+				if ( typeof id === 'number' ) node.id = id;
+				if ( attrName !== '' ) node.attrName = attrName;
+				if ( attrType !== '' ) node.attrType = attrType;
+				if ( name !== '' ) node.name = name;
+				return node;
 
-					node[ subNode.name ] = {};
-					node[ subNode.name ][ subNode.id ] = subNode;
-
-				} else {
+			},
+			parseSubNode: function ( name, node, subNode ) {
 
-					node[ subNode.name ] = subNode;
+				// special case: child node is single property
+				if ( subNode.singleProperty === true ) {
 
-				}
+					var value = subNode.propertyList[ 0 ];
 
-			} else {
+					if ( Array.isArray( value ) ) {
 
-				if ( subNode.name === 'PoseNode' ) {
+						node[ subNode.name ] = subNode;
+						subNode.a = value;
 
-					if ( ! Array.isArray( node[ subNode.name ] ) ) {
+					} else {
 
-						node[ subNode.name ] = [ node[ subNode.name ] ];
+						node[ subNode.name ] = value;
 
 					}
 
-					node[ subNode.name ].push( subNode );
-
-				} else if ( node[ subNode.name ][ subNode.id ] === undefined ) {
+				} else if ( name === 'Connections' && subNode.name === 'C' ) {
 
-					node[ subNode.name ][ subNode.id ] = subNode;
+					var array = [];
+					subNode.propertyList.forEach( function ( property, i ) {
 
-				}
+						// first Connection is FBX type (OO, OP, etc.). We'll discard these
+						if ( i !== 0 ) array.push( property );
 
-			}
+					} );
 
-		},
+					if ( node.connections === undefined ) {
 
-		parseProperty: function ( reader ) {
+						node.connections = [];
 
-			var type = reader.getString( 1 );
+					}
 
-			switch ( type ) {
+					node.connections.push( array );
 
-				case 'C':
-					return reader.getBoolean();
+				} else if ( subNode.name === 'Properties70' ) {
 
-				case 'D':
-					return reader.getFloat64();
+					var keys = Object.keys( subNode );
+					keys.forEach( function ( key ) {
 
-				case 'F':
-					return reader.getFloat32();
+						node[ key ] = subNode[ key ];
 
-				case 'I':
-					return reader.getInt32();
+					} );
 
-				case 'L':
-					return reader.getInt64();
+				} else if ( name === 'Properties70' && subNode.name === 'P' ) {
 
-				case 'R':
-					var length = reader.getUint32();
-					return reader.getArrayBuffer( length );
+					var innerPropName = subNode.propertyList[ 0 ];
+					var innerPropType1 = subNode.propertyList[ 1 ];
+					var innerPropType2 = subNode.propertyList[ 2 ];
+					var innerPropFlag = subNode.propertyList[ 3 ];
+					var innerPropValue;
+					if ( innerPropName.indexOf( 'Lcl ' ) === 0 ) innerPropName = innerPropName.replace( 'Lcl ', 'Lcl_' );
+					if ( innerPropType1.indexOf( 'Lcl ' ) === 0 ) innerPropType1 = innerPropType1.replace( 'Lcl ', 'Lcl_' );
 
-				case 'S':
-					var length = reader.getUint32();
-					return reader.getString( length );
+					if ( innerPropType1 === 'Color' || innerPropType1 === 'ColorRGB' || innerPropType1 === 'Vector' || innerPropType1 === 'Vector3D' || innerPropType1.indexOf( 'Lcl_' ) === 0 ) {
 
-				case 'Y':
-					return reader.getInt16();
+						innerPropValue = [ subNode.propertyList[ 4 ], subNode.propertyList[ 5 ], subNode.propertyList[ 6 ] ];
 
-				case 'b':
-				case 'c':
-				case 'd':
-				case 'f':
-				case 'i':
-				case 'l':
+					} else {
 
-					var arrayLength = reader.getUint32();
-					var encoding = reader.getUint32(); // 0: non-compressed, 1: compressed
-					var compressedLength = reader.getUint32();
+						innerPropValue = subNode.propertyList[ 4 ];
 
-					if ( encoding === 0 ) {
+					} // this will be copied to parent, see above
 
-						switch ( type ) {
 
-							case 'b':
-							case 'c':
-								return reader.getBooleanArray( arrayLength );
+					node[ innerPropName ] = {
+						'type': innerPropType1,
+						'type2': innerPropType2,
+						'flag': innerPropFlag,
+						'value': innerPropValue
+					};
 
-							case 'd':
-								return reader.getFloat64Array( arrayLength );
+				} else if ( node[ subNode.name ] === undefined ) {
 
-							case 'f':
-								return reader.getFloat32Array( arrayLength );
+					if ( typeof subNode.id === 'number' ) {
 
-							case 'i':
-								return reader.getInt32Array( arrayLength );
+						node[ subNode.name ] = {};
+						node[ subNode.name ][ subNode.id ] = subNode;
 
-							case 'l':
-								return reader.getInt64Array( arrayLength );
+					} else {
 
-						}
+						node[ subNode.name ] = subNode;
 
 					}
 
-					if ( typeof fflate === 'undefined' ) {
-
-						console.error( 'THREE.FBXLoader: External library fflate.min.js required.' );
-
-					}
+				} else {
 
-					var data = fflate.unzlibSync( new Uint8Array( reader.getArrayBuffer( compressedLength ) ) ); // eslint-disable-line no-undef
-					var reader2 = new BinaryReader( data.buffer );
+					if ( subNode.name === 'PoseNode' ) {
 
-					switch ( type ) {
+						if ( ! Array.isArray( node[ subNode.name ] ) ) {
 
-						case 'b':
-						case 'c':
-							return reader2.getBooleanArray( arrayLength );
+							node[ subNode.name ] = [ node[ subNode.name ] ];
 
-						case 'd':
-							return reader2.getFloat64Array( arrayLength );
+						}
 
-						case 'f':
-							return reader2.getFloat32Array( arrayLength );
+						node[ subNode.name ].push( subNode );
 
-						case 'i':
-							return reader2.getInt32Array( arrayLength );
+					} else if ( node[ subNode.name ][ subNode.id ] === undefined ) {
 
-						case 'l':
-							return reader2.getInt64Array( arrayLength );
+						node[ subNode.name ][ subNode.id ] = subNode;
 
 					}
 
-				default:
-					throw new Error( 'THREE.FBXLoader: Unknown property type ' + type );
-
-			}
+				}
 
-		}
+			},
+			parseProperty: function ( reader ) {
 
-	};
+				var type = reader.getString( 1 );
 
-	function BinaryReader( buffer, littleEndian ) {
+				switch ( type ) {
 
-		this.dv = new DataView( buffer );
-		this.offset = 0;
-		this.littleEndian = ( littleEndian !== undefined ) ? littleEndian : true;
+					case 'C':
+						return reader.getBoolean();
 
-	}
+					case 'D':
+						return reader.getFloat64();
 
-	BinaryReader.prototype = {
+					case 'F':
+						return reader.getFloat32();
 
-		constructor: BinaryReader,
+					case 'I':
+						return reader.getInt32();
 
-		getOffset: function () {
+					case 'L':
+						return reader.getInt64();
 
-			return this.offset;
+					case 'R':
+						var length = reader.getUint32();
+						return reader.getArrayBuffer( length );
 
-		},
+					case 'S':
+						var length = reader.getUint32();
+						return reader.getString( length );
 
-		size: function () {
+					case 'Y':
+						return reader.getInt16();
 
-			return this.dv.buffer.byteLength;
+					case 'b':
+					case 'c':
+					case 'd':
+					case 'f':
+					case 'i':
+					case 'l':
+						var arrayLength = reader.getUint32();
+						var encoding = reader.getUint32(); // 0: non-compressed, 1: compressed
 
-		},
+						var compressedLength = reader.getUint32();
 
-		skip: function ( length ) {
+						if ( encoding === 0 ) {
 
-			this.offset += length;
+							switch ( type ) {
 
-		},
+								case 'b':
+								case 'c':
+									return reader.getBooleanArray( arrayLength );
 
-		// seems like true/false representation depends on exporter.
-		// true: 1 or 'Y'(=0x59), false: 0 or 'T'(=0x54)
-		// then sees LSB.
-		getBoolean: function () {
+								case 'd':
+									return reader.getFloat64Array( arrayLength );
 
-			return ( this.getUint8() & 1 ) === 1;
+								case 'f':
+									return reader.getFloat32Array( arrayLength );
 
-		},
+								case 'i':
+									return reader.getInt32Array( arrayLength );
 
-		getBooleanArray: function ( size ) {
+								case 'l':
+									return reader.getInt64Array( arrayLength );
 
-			var a = [];
+							}
 
-			for ( var i = 0; i < size; i ++ ) {
+						}
 
-				a.push( this.getBoolean() );
+						if ( typeof fflate === 'undefined' ) {
 
-			}
+							console.error( 'THREE.FBXLoader: External library fflate.min.js required.' );
 
-			return a;
+						}
 
-		},
+						var data = fflate.unzlibSync( new Uint8Array( reader.getArrayBuffer( compressedLength ) ) ); // eslint-disable-line no-undef
 
-		getUint8: function () {
+						var reader2 = new BinaryReader( data.buffer );
 
-			var value = this.dv.getUint8( this.offset );
-			this.offset += 1;
-			return value;
+						switch ( type ) {
 
-		},
+							case 'b':
+							case 'c':
+								return reader2.getBooleanArray( arrayLength );
 
-		getInt16: function () {
+							case 'd':
+								return reader2.getFloat64Array( arrayLength );
 
-			var value = this.dv.getInt16( this.offset, this.littleEndian );
-			this.offset += 2;
-			return value;
+							case 'f':
+								return reader2.getFloat32Array( arrayLength );
 
-		},
+							case 'i':
+								return reader2.getInt32Array( arrayLength );
 
-		getInt32: function () {
+							case 'l':
+								return reader2.getInt64Array( arrayLength );
 
-			var value = this.dv.getInt32( this.offset, this.littleEndian );
-			this.offset += 4;
-			return value;
+						}
 
-		},
+					default:
+						throw new Error( 'THREE.FBXLoader: Unknown property type ' + type );
 
-		getInt32Array: function ( size ) {
+				}
 
-			var a = [];
+			}
+		};
 
-			for ( var i = 0; i < size; i ++ ) {
+		function BinaryReader( buffer, littleEndian ) {
 
-				a.push( this.getInt32() );
+			this.dv = new DataView( buffer );
+			this.offset = 0;
+			this.littleEndian = littleEndian !== undefined ? littleEndian : true;
 
-			}
+		}
 
-			return a;
+		BinaryReader.prototype = {
+			constructor: BinaryReader,
+			getOffset: function () {
 
-		},
+				return this.offset;
 
-		getUint32: function () {
+			},
+			size: function () {
 
-			var value = this.dv.getUint32( this.offset, this.littleEndian );
-			this.offset += 4;
-			return value;
+				return this.dv.buffer.byteLength;
 
-		},
+			},
+			skip: function ( length ) {
 
-		// JavaScript doesn't support 64-bit integer so calculate this here
-		// 1 << 32 will return 1 so using multiply operation instead here.
-		// There's a possibility that this method returns wrong value if the value
-		// is out of the range between Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER.
-		// TODO: safely handle 64-bit integer
-		getInt64: function () {
+				this.offset += length;
 
-			var low, high;
+			},
+			// seems like true/false representation depends on exporter.
+			// true: 1 or 'Y'(=0x59), false: 0 or 'T'(=0x54)
+			// then sees LSB.
+			getBoolean: function () {
 
-			if ( this.littleEndian ) {
+				return ( this.getUint8() & 1 ) === 1;
 
-				low = this.getUint32();
-				high = this.getUint32();
+			},
+			getBooleanArray: function ( size ) {
 
-			} else {
+				var a = [];
 
-				high = this.getUint32();
-				low = this.getUint32();
+				for ( var i = 0; i < size; i ++ ) {
 
-			}
+					a.push( this.getBoolean() );
 
-			// calculate negative value
-			if ( high & 0x80000000 ) {
+				}
 
-				high = ~ high & 0xFFFFFFFF;
-				low = ~ low & 0xFFFFFFFF;
+				return a;
 
-				if ( low === 0xFFFFFFFF ) high = ( high + 1 ) & 0xFFFFFFFF;
+			},
+			getUint8: function () {
 
-				low = ( low + 1 ) & 0xFFFFFFFF;
+				var value = this.dv.getUint8( this.offset );
+				this.offset += 1;
+				return value;
 
-				return - ( high * 0x100000000 + low );
+			},
+			getInt16: function () {
 
-			}
+				var value = this.dv.getInt16( this.offset, this.littleEndian );
+				this.offset += 2;
+				return value;
 
-			return high * 0x100000000 + low;
+			},
+			getInt32: function () {
 
-		},
+				var value = this.dv.getInt32( this.offset, this.littleEndian );
+				this.offset += 4;
+				return value;
 
-		getInt64Array: function ( size ) {
+			},
+			getInt32Array: function ( size ) {
 
-			var a = [];
+				var a = [];
 
-			for ( var i = 0; i < size; i ++ ) {
+				for ( var i = 0; i < size; i ++ ) {
 
-				a.push( this.getInt64() );
+					a.push( this.getInt32() );
 
-			}
+				}
 
-			return a;
+				return a;
 
-		},
+			},
+			getUint32: function () {
 
-		// Note: see getInt64() comment
-		getUint64: function () {
+				var value = this.dv.getUint32( this.offset, this.littleEndian );
+				this.offset += 4;
+				return value;
 
-			var low, high;
+			},
+			// JavaScript doesn't support 64-bit integer so calculate this here
+			// 1 << 32 will return 1 so using multiply operation instead here.
+			// There's a possibility that this method returns wrong value if the value
+			// is out of the range between Number.MAX_SAFE_INTEGER and Number.MIN_SAFE_INTEGER.
+			// TODO: safely handle 64-bit integer
+			getInt64: function () {
 
-			if ( this.littleEndian ) {
+				var low, high;
 
-				low = this.getUint32();
-				high = this.getUint32();
+				if ( this.littleEndian ) {
 
-			} else {
+					low = this.getUint32();
+					high = this.getUint32();
 
-				high = this.getUint32();
-				low = this.getUint32();
+				} else {
 
-			}
+					high = this.getUint32();
+					low = this.getUint32();
 
-			return high * 0x100000000 + low;
+				} // calculate negative value
 
-		},
 
-		getFloat32: function () {
+				if ( high & 0x80000000 ) {
 
-			var value = this.dv.getFloat32( this.offset, this.littleEndian );
-			this.offset += 4;
-			return value;
+					high = ~ high & 0xFFFFFFFF;
+					low = ~ low & 0xFFFFFFFF;
+					if ( low === 0xFFFFFFFF ) high = high + 1 & 0xFFFFFFFF;
+					low = low + 1 & 0xFFFFFFFF;
+					return - ( high * 0x100000000 + low );
 
-		},
+				}
 
-		getFloat32Array: function ( size ) {
+				return high * 0x100000000 + low;
 
-			var a = [];
+			},
+			getInt64Array: function ( size ) {
 
-			for ( var i = 0; i < size; i ++ ) {
+				var a = [];
 
-				a.push( this.getFloat32() );
+				for ( var i = 0; i < size; i ++ ) {
 
-			}
+					a.push( this.getInt64() );
 
-			return a;
+				}
 
-		},
+				return a;
 
-		getFloat64: function () {
+			},
+			// Note: see getInt64() comment
+			getUint64: function () {
 
-			var value = this.dv.getFloat64( this.offset, this.littleEndian );
-			this.offset += 8;
-			return value;
+				var low, high;
 
-		},
+				if ( this.littleEndian ) {
 
-		getFloat64Array: function ( size ) {
+					low = this.getUint32();
+					high = this.getUint32();
 
-			var a = [];
+				} else {
 
-			for ( var i = 0; i < size; i ++ ) {
+					high = this.getUint32();
+					low = this.getUint32();
 
-				a.push( this.getFloat64() );
+				}
 
-			}
+				return high * 0x100000000 + low;
 
-			return a;
+			},
+			getFloat32: function () {
 
-		},
+				var value = this.dv.getFloat32( this.offset, this.littleEndian );
+				this.offset += 4;
+				return value;
 
-		getArrayBuffer: function ( size ) {
+			},
+			getFloat32Array: function ( size ) {
 
-			var value = this.dv.buffer.slice( this.offset, this.offset + size );
-			this.offset += size;
-			return value;
+				var a = [];
 
-		},
+				for ( var i = 0; i < size; i ++ ) {
 
-		getString: function ( size ) {
+					a.push( this.getFloat32() );
 
-			// note: safari 9 doesn't support Uint8Array.indexOf; create intermediate array instead
-			var a = [];
+				}
 
-			for ( var i = 0; i < size; i ++ ) {
+				return a;
 
-				a[ i ] = this.getUint8();
+			},
+			getFloat64: function () {
 
-			}
+				var value = this.dv.getFloat64( this.offset, this.littleEndian );
+				this.offset += 8;
+				return value;
 
-			var nullByte = a.indexOf( 0 );
-			if ( nullByte >= 0 ) a = a.slice( 0, nullByte );
+			},
+			getFloat64Array: function ( size ) {
 
-			return THREE.LoaderUtils.decodeText( new Uint8Array( a ) );
+				var a = [];
 
-		}
+				for ( var i = 0; i < size; i ++ ) {
 
-	};
+					a.push( this.getFloat64() );
 
-	// FBXTree holds a representation of the FBX data, returned by the TextParser ( FBX ASCII format)
-	// and BinaryParser( FBX Binary format)
-	function FBXTree() {}
+				}
 
-	FBXTree.prototype = {
+				return a;
 
-		constructor: FBXTree,
+			},
+			getArrayBuffer: function ( size ) {
 
-		add: function ( key, val ) {
+				var value = this.dv.buffer.slice( this.offset, this.offset + size );
+				this.offset += size;
+				return value;
 
-			this[ key ] = val;
+			},
+			getString: function ( size ) {
 
-		},
+				// note: safari 9 doesn't support Uint8Array.indexOf; create intermediate array instead
+				var a = [];
 
-	};
+				for ( var i = 0; i < size; i ++ ) {
 
-	// ************** UTILITY FUNCTIONS **************
+					a[ i ] = this.getUint8();
 
-	function isFbxFormatBinary( buffer ) {
+				}
 
-		var CORRECT = 'Kaydara FBX Binary  \0';
+				var nullByte = a.indexOf( 0 );
+				if ( nullByte >= 0 ) a = a.slice( 0, nullByte );
+				return THREE.LoaderUtils.decodeText( new Uint8Array( a ) );
 
-		return buffer.byteLength >= CORRECT.length && CORRECT === convertArrayBufferToString( buffer, 0, CORRECT.length );
+			}
+		}; // FBXTree holds a representation of the FBX data, returned by the TextParser ( FBX ASCII format)
+		// and BinaryParser( FBX Binary format)
 
-	}
+		function FBXTree() {}
 
-	function isFbxFormatASCII( text ) {
+		FBXTree.prototype = {
+			constructor: FBXTree,
+			add: function ( key, val ) {
 
-		var CORRECT = [ 'K', 'a', 'y', 'd', 'a', 'r', 'a', '\\', 'F', 'B', 'X', '\\', 'B', 'i', 'n', 'a', 'r', 'y', '\\', '\\' ];
+				this[ key ] = val;
 
-		var cursor = 0;
+			}
+		}; // ************** UTILITY FUNCTIONS **************
 
-		function read( offset ) {
+		function isFbxFormatBinary( buffer ) {
 
-			var result = text[ offset - 1 ];
-			text = text.slice( cursor + offset );
-			cursor ++;
-			return result;
+			var CORRECT = 'Kaydara FBX Binary	\0';
+			return buffer.byteLength >= CORRECT.length && CORRECT === convertArrayBufferToString( buffer, 0, CORRECT.length );
 
 		}
 
-		for ( var i = 0; i < CORRECT.length; ++ i ) {
+		function isFbxFormatASCII( text ) {
 
-			var num = read( 1 );
-			if ( num === CORRECT[ i ] ) {
+			var CORRECT = [ 'K', 'a', 'y', 'd', 'a', 'r', 'a', '\\', 'F', 'B', 'X', '\\', 'B', 'i', 'n', 'a', 'r', 'y', '\\', '\\' ];
+			var cursor = 0;
 
-				return false;
+			function read( offset ) {
+
+				var result = text[ offset - 1 ];
+				text = text.slice( cursor + offset );
+				cursor ++;
+				return result;
 
 			}
 
-		}
+			for ( var i = 0; i < CORRECT.length; ++ i ) {
 
-		return true;
+				var num = read( 1 );
 
-	}
+				if ( num === CORRECT[ i ] ) {
 
-	function getFbxVersion( text ) {
+					return false;
 
-		var versionRegExp = /FBXVersion: (\d+)/;
-		var match = text.match( versionRegExp );
+				}
 
-		if ( match ) {
+			}
 
-			var version = parseInt( match[ 1 ] );
-			return version;
+			return true;
 
 		}
 
-		throw new Error( 'THREE.FBXLoader: Cannot find the version number for the file given.' );
+		function getFbxVersion( text ) {
 
-	}
+			var versionRegExp = /FBXVersion: (\d+)/;
+			var match = text.match( versionRegExp );
 
-	// Converts FBX ticks into real time seconds.
-	function convertFBXTimeToSeconds( time ) {
+			if ( match ) {
 
-		return time / 46186158000;
+				var version = parseInt( match[ 1 ] );
+				return version;
 
-	}
+			}
 
-	var dataArray = [];
+			throw new Error( 'THREE.FBXLoader: Cannot find the version number for the file given.' );
 
-	// extracts the data from the correct position in the FBX array based on indexing type
-	function getData( polygonVertexIndex, polygonIndex, vertexIndex, infoObject ) {
+		} // Converts FBX ticks into real time seconds.
 
-		var index;
 
-		switch ( infoObject.mappingType ) {
+		function convertFBXTimeToSeconds( time ) {
 
-			case 'ByPolygonVertex' :
-				index = polygonVertexIndex;
-				break;
-			case 'ByPolygon' :
-				index = polygonIndex;
-				break;
-			case 'ByVertice' :
-				index = vertexIndex;
-				break;
-			case 'AllSame' :
-				index = infoObject.indices[ 0 ];
-				break;
-			default :
-				console.warn( 'THREE.FBXLoader: unknown attribute mapping type ' + infoObject.mappingType );
+			return time / 46186158000;
 
 		}
 
-		if ( infoObject.referenceType === 'IndexToDirect' ) index = infoObject.indices[ index ];
-
-		var from = index * infoObject.dataSize;
-		var to = from + infoObject.dataSize;
+		var dataArray = []; // extracts the data from the correct position in the FBX array based on indexing type
 
-		return slice( dataArray, infoObject.buffer, from, to );
+		function getData( polygonVertexIndex, polygonIndex, vertexIndex, infoObject ) {
 
-	}
+			var index;
 
-	var tempEuler = new THREE.Euler();
-	var tempVec = new THREE.Vector3();
+			switch ( infoObject.mappingType ) {
 
-	// generate transformation from FBX transform data
-	// ref: https://help.autodesk.com/view/FBX/2017/ENU/?guid=__files_GUID_10CDD63C_79C1_4F2D_BB28_AD2BE65A02ED_htm
-	// ref: http://docs.autodesk.com/FBX/2014/ENU/FBX-SDK-Documentation/index.html?url=cpp_ref/_transformations_2main_8cxx-example.html,topicNumber=cpp_ref__transformations_2main_8cxx_example_htmlfc10a1e1-b18d-4e72-9dc0-70d0f1959f5e
-	function generateTransform( transformData ) {
-
-		var lTranslationM = new THREE.Matrix4();
-		var lPreRotationM = new THREE.Matrix4();
-		var lRotationM = new THREE.Matrix4();
-		var lPostRotationM = new THREE.Matrix4();
+				case 'ByPolygonVertex':
+					index = polygonVertexIndex;
+					break;
 
-		var lScalingM = new THREE.Matrix4();
-		var lScalingPivotM = new THREE.Matrix4();
-		var lScalingOffsetM = new THREE.Matrix4();
-		var lRotationOffsetM = new THREE.Matrix4();
-		var lRotationPivotM = new THREE.Matrix4();
+				case 'ByPolygon':
+					index = polygonIndex;
+					break;
 
-		var lParentGX = new THREE.Matrix4();
-		var lParentLX = new THREE.Matrix4();
-		var lGlobalT = new THREE.Matrix4();
+				case 'ByVertice':
+					index = vertexIndex;
+					break;
 
-		var inheritType = ( transformData.inheritType ) ? transformData.inheritType : 0;
+				case 'AllSame':
+					index = infoObject.indices[ 0 ];
+					break;
 
-		if ( transformData.translation ) lTranslationM.setPosition( tempVec.fromArray( transformData.translation ) );
+				default:
+					console.warn( 'THREE.FBXLoader: unknown attribute mapping type ' + infoObject.mappingType );
 
-		if ( transformData.preRotation ) {
+			}
 
-			var array = transformData.preRotation.map( THREE.MathUtils.degToRad );
-			array.push( transformData.eulerOrder );
-			lPreRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) );
+			if ( infoObject.referenceType === 'IndexToDirect' ) index = infoObject.indices[ index ];
+			var from = index * infoObject.dataSize;
+			var to = from + infoObject.dataSize;
+			return slice( dataArray, infoObject.buffer, from, to );
 
 		}
 
-		if ( transformData.rotation ) {
+		var tempEuler = new THREE.Euler();
+		var tempVec = new THREE.Vector3(); // generate transformation from FBX transform data
+		// ref: https://help.autodesk.com/view/FBX/2017/ENU/?guid=__files_GUID_10CDD63C_79C1_4F2D_BB28_AD2BE65A02ED_htm
+		// ref: http://docs.autodesk.com/FBX/2014/ENU/FBX-SDK-Documentation/index.html?url=cpp_ref/_transformations_2main_8cxx-example.html,topicNumber=cpp_ref__transformations_2main_8cxx_example_htmlfc10a1e1-b18d-4e72-9dc0-70d0f1959f5e
 
-			var array = transformData.rotation.map( THREE.MathUtils.degToRad );
-			array.push( transformData.eulerOrder );
-			lRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) );
+		function generateTransform( transformData ) {
 
-		}
+			var lTranslationM = new THREE.Matrix4();
+			var lPreRotationM = new THREE.Matrix4();
+			var lRotationM = new THREE.Matrix4();
+			var lPostRotationM = new THREE.Matrix4();
+			var lScalingM = new THREE.Matrix4();
+			var lScalingPivotM = new THREE.Matrix4();
+			var lScalingOffsetM = new THREE.Matrix4();
+			var lRotationOffsetM = new THREE.Matrix4();
+			var lRotationPivotM = new THREE.Matrix4();
+			var lParentGX = new THREE.Matrix4();
+			var lParentLX = new THREE.Matrix4();
+			var lGlobalT = new THREE.Matrix4();
+			var inheritType = transformData.inheritType ? transformData.inheritType : 0;
+			if ( transformData.translation ) lTranslationM.setPosition( tempVec.fromArray( transformData.translation ) );
 
-		if ( transformData.postRotation ) {
+			if ( transformData.preRotation ) {
 
-			var array = transformData.postRotation.map( THREE.MathUtils.degToRad );
-			array.push( transformData.eulerOrder );
-			lPostRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) );
-			lPostRotationM.invert();
+				var array = transformData.preRotation.map( THREE.MathUtils.degToRad );
+				array.push( transformData.eulerOrder );
+				lPreRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) );
 
-		}
+			}
 
-		if ( transformData.scale ) lScalingM.scale( tempVec.fromArray( transformData.scale ) );
+			if ( transformData.rotation ) {
 
-		// Pivots and offsets
-		if ( transformData.scalingOffset ) lScalingOffsetM.setPosition( tempVec.fromArray( transformData.scalingOffset ) );
-		if ( transformData.scalingPivot ) lScalingPivotM.setPosition( tempVec.fromArray( transformData.scalingPivot ) );
-		if ( transformData.rotationOffset ) lRotationOffsetM.setPosition( tempVec.fromArray( transformData.rotationOffset ) );
-		if ( transformData.rotationPivot ) lRotationPivotM.setPosition( tempVec.fromArray( transformData.rotationPivot ) );
+				var array = transformData.rotation.map( THREE.MathUtils.degToRad );
+				array.push( transformData.eulerOrder );
+				lRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) );
 
-		// parent transform
-		if ( transformData.parentMatrixWorld ) {
+			}
 
-			lParentLX.copy( transformData.parentMatrix );
-			lParentGX.copy( transformData.parentMatrixWorld );
+			if ( transformData.postRotation ) {
 
-		}
+				var array = transformData.postRotation.map( THREE.MathUtils.degToRad );
+				array.push( transformData.eulerOrder );
+				lPostRotationM.makeRotationFromEuler( tempEuler.fromArray( array ) );
+				lPostRotationM.invert();
+
+			}
 
-		var lLRM = new THREE.Matrix4().copy( lPreRotationM ).multiply( lRotationM ).multiply( lPostRotationM );
-		// Global Rotation
-		var lParentGRM = new THREE.Matrix4();
-		lParentGRM.extractRotation( lParentGX );
+			if ( transformData.scale ) lScalingM.scale( tempVec.fromArray( transformData.scale ) ); // Pivots and offsets
 
-		// Global Shear*Scaling
-		var lParentTM = new THREE.Matrix4();
-		lParentTM.copyPosition( lParentGX );
+			if ( transformData.scalingOffset ) lScalingOffsetM.setPosition( tempVec.fromArray( transformData.scalingOffset ) );
+			if ( transformData.scalingPivot ) lScalingPivotM.setPosition( tempVec.fromArray( transformData.scalingPivot ) );
+			if ( transformData.rotationOffset ) lRotationOffsetM.setPosition( tempVec.fromArray( transformData.rotationOffset ) );
+			if ( transformData.rotationPivot ) lRotationPivotM.setPosition( tempVec.fromArray( transformData.rotationPivot ) ); // parent transform
 
-		var lParentGSM = new THREE.Matrix4();
-		var lParentGRSM = new THREE.Matrix4().copy( lParentTM ).invert().multiply( lParentGX );
-		lParentGSM.copy( lParentGRM ).invert().multiply( lParentGRSM );
-		var lLSM = lScalingM;
+			if ( transformData.parentMatrixWorld ) {
 
-		var lGlobalRS = new THREE.Matrix4();
+				lParentLX.copy( transformData.parentMatrix );
+				lParentGX.copy( transformData.parentMatrixWorld );
 
-		if ( inheritType === 0 ) {
+			}
 
-			lGlobalRS.copy( lParentGRM ).multiply( lLRM ).multiply( lParentGSM ).multiply( lLSM );
+			var lLRM = new THREE.Matrix4().copy( lPreRotationM ).multiply( lRotationM ).multiply( lPostRotationM ); // Global Rotation
 
-		} else if ( inheritType === 1 ) {
+			var lParentGRM = new THREE.Matrix4();
+			lParentGRM.extractRotation( lParentGX ); // Global Shear*Scaling
 
-			lGlobalRS.copy( lParentGRM ).multiply( lParentGSM ).multiply( lLRM ).multiply( lLSM );
+			var lParentTM = new THREE.Matrix4();
+			lParentTM.copyPosition( lParentGX );
+			var lParentGSM = new THREE.Matrix4();
+			var lParentGRSM = new THREE.Matrix4().copy( lParentTM ).invert().multiply( lParentGX );
+			lParentGSM.copy( lParentGRM ).invert().multiply( lParentGRSM );
+			var lLSM = lScalingM;
+			var lGlobalRS = new THREE.Matrix4();
 
-		} else {
+			if ( inheritType === 0 ) {
 
-			var lParentLSM = new THREE.Matrix4().scale( new THREE.Vector3().setFromMatrixScale( lParentLX ) );
-			var lParentLSM_inv = new THREE.Matrix4().copy( lParentLSM ).invert();
-			var lParentGSM_noLocal = new THREE.Matrix4().copy( lParentGSM ).multiply( lParentLSM_inv );
+				lGlobalRS.copy( lParentGRM ).multiply( lLRM ).multiply( lParentGSM ).multiply( lLSM );
 
-			lGlobalRS.copy( lParentGRM ).multiply( lLRM ).multiply( lParentGSM_noLocal ).multiply( lLSM );
+			} else if ( inheritType === 1 ) {
 
-		}
+				lGlobalRS.copy( lParentGRM ).multiply( lParentGSM ).multiply( lLRM ).multiply( lLSM );
 
-		var lRotationPivotM_inv = new THREE.Matrix4();
-		lRotationPivotM_inv.copy( lRotationPivotM ).invert();
-		var lScalingPivotM_inv = new THREE.Matrix4();
-		lScalingPivotM_inv.copy( lScalingPivotM ).invert();
-		// Calculate the local transform matrix
-		var lTransform = new THREE.Matrix4();
-		lTransform.copy( lTranslationM ).multiply( lRotationOffsetM ).multiply( lRotationPivotM ).multiply( lPreRotationM ).multiply( lRotationM ).multiply( lPostRotationM ).multiply( lRotationPivotM_inv ).multiply( lScalingOffsetM ).multiply( lScalingPivotM ).multiply( lScalingM ).multiply( lScalingPivotM_inv );
+			} else {
 
-		var lLocalTWithAllPivotAndOffsetInfo = new THREE.Matrix4().copyPosition( lTransform );
+				var lParentLSM = new THREE.Matrix4().scale( new THREE.Vector3().setFromMatrixScale( lParentLX ) );
+				var lParentLSM_inv = new THREE.Matrix4().copy( lParentLSM ).invert();
+				var lParentGSM_noLocal = new THREE.Matrix4().copy( lParentGSM ).multiply( lParentLSM_inv );
+				lGlobalRS.copy( lParentGRM ).multiply( lLRM ).multiply( lParentGSM_noLocal ).multiply( lLSM );
 
-		var lGlobalTranslation = new THREE.Matrix4().copy( lParentGX ).multiply( lLocalTWithAllPivotAndOffsetInfo );
-		lGlobalT.copyPosition( lGlobalTranslation );
+			}
 
-		lTransform = new THREE.Matrix4().copy( lGlobalT ).multiply( lGlobalRS );
+			var lRotationPivotM_inv = new THREE.Matrix4();
+			lRotationPivotM_inv.copy( lRotationPivotM ).invert();
+			var lScalingPivotM_inv = new THREE.Matrix4();
+			lScalingPivotM_inv.copy( lScalingPivotM ).invert(); // Calculate the local transform matrix
 
-		// from global to local
-		lTransform.premultiply( lParentGX.invert() );
+			var lTransform = new THREE.Matrix4();
+			lTransform.copy( lTranslationM ).multiply( lRotationOffsetM ).multiply( lRotationPivotM ).multiply( lPreRotationM ).multiply( lRotationM ).multiply( lPostRotationM ).multiply( lRotationPivotM_inv ).multiply( lScalingOffsetM ).multiply( lScalingPivotM ).multiply( lScalingM ).multiply( lScalingPivotM_inv );
+			var lLocalTWithAllPivotAndOffsetInfo = new THREE.Matrix4().copyPosition( lTransform );
+			var lGlobalTranslation = new THREE.Matrix4().copy( lParentGX ).multiply( lLocalTWithAllPivotAndOffsetInfo );
+			lGlobalT.copyPosition( lGlobalTranslation );
+			lTransform = new THREE.Matrix4().copy( lGlobalT ).multiply( lGlobalRS ); // from global to local
 
-		return lTransform;
+			lTransform.premultiply( lParentGX.invert() );
+			return lTransform;
 
-	}
+		} // Returns the three.js intrinsic THREE.Euler order corresponding to FBX extrinsic THREE.Euler order
+		// ref: http://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_euler_html
 
-	// Returns the three.js intrinsic Euler order corresponding to FBX extrinsic Euler order
-	// ref: http://help.autodesk.com/view/FBX/2017/ENU/?guid=__cpp_ref_class_fbx_euler_html
-	function getEulerOrder( order ) {
 
-		order = order || 0;
+		function getEulerOrder( order ) {
 
-		var enums = [
-			'ZYX', // -> XYZ extrinsic
-			'YZX', // -> XZY extrinsic
-			'XZY', // -> YZX extrinsic
-			'ZXY', // -> YXZ extrinsic
-			'YXZ', // -> ZXY extrinsic
-			'XYZ', // -> ZYX extrinsic
-			//'SphericXYZ', // not possible to support
-		];
+			order = order || 0;
+			var enums = [ 'ZYX', // -> XYZ extrinsic
+				'YZX', // -> XZY extrinsic
+				'XZY', // -> YZX extrinsic
+				'ZXY', // -> YXZ extrinsic
+				'YXZ', // -> ZXY extrinsic
+				'XYZ' // -> ZYX extrinsic
+				//'SphericXYZ', // not possible to support
+			];
 
-		if ( order === 6 ) {
+			if ( order === 6 ) {
 
-			console.warn( 'THREE.FBXLoader: unsupported Euler Order: Spherical XYZ. Animations and rotations may be incorrect.' );
-			return enums[ 0 ];
+				console.warn( 'THREE.FBXLoader: unsupported THREE.Euler Order: Spherical XYZ. Animations and rotations may be incorrect.' );
+				return enums[ 0 ];
 
-		}
+			}
 
-		return enums[ order ];
+			return enums[ order ];
 
-	}
+		} // Parses comma separated list of numbers and returns them an array.
+		// Used internally by the TextParser
 
-	// Parses comma separated list of numbers and returns them an array.
-	// Used internally by the TextParser
-	function parseNumberArray( value ) {
 
-		var array = value.split( ',' ).map( function ( val ) {
+		function parseNumberArray( value ) {
 
-			return parseFloat( val );
+			var array = value.split( ',' ).map( function ( val ) {
 
-		} );
+				return parseFloat( val );
 
-		return array;
+			} );
+			return array;
 
-	}
+		}
 
-	function convertArrayBufferToString( buffer, from, to ) {
+		function convertArrayBufferToString( buffer, from, to ) {
 
-		if ( from === undefined ) from = 0;
-		if ( to === undefined ) to = buffer.byteLength;
+			if ( from === undefined ) from = 0;
+			if ( to === undefined ) to = buffer.byteLength;
+			return THREE.LoaderUtils.decodeText( new Uint8Array( buffer, from, to ) );
 
-		return THREE.LoaderUtils.decodeText( new Uint8Array( buffer, from, to ) );
+		}
 
-	}
+		function append( a, b ) {
 
-	function append( a, b ) {
+			for ( var i = 0, j = a.length, l = b.length; i < l; i ++, j ++ ) {
 
-		for ( var i = 0, j = a.length, l = b.length; i < l; i ++, j ++ ) {
+				a[ j ] = b[ i ];
 
-			a[ j ] = b[ i ];
+			}
 
 		}
 
-	}
+		function slice( a, b, from, to ) {
 
-	function slice( a, b, from, to ) {
+			for ( var i = from, j = 0; i < to; i ++, j ++ ) {
 
-		for ( var i = from, j = 0; i < to; i ++, j ++ ) {
+				a[ j ] = b[ i ];
 
-			a[ j ] = b[ i ];
+			}
 
-		}
+			return a;
+
+		} // inject array a2 into array a1 at index
 
-		return a;
 
-	}
+		function inject( a1, index, a2 ) {
 
-	// inject array a2 into array a1 at index
-	function inject( a1, index, a2 ) {
+			return a1.slice( 0, index ).concat( a2 ).concat( a1.slice( index ) );
+
+		}
 
-		return a1.slice( 0, index ).concat( a2 ).concat( a1.slice( index ) );
+		return FBXLoader;
 
-	}
+	}();
 
-	return FBXLoader;
+	THREE.FBXLoader = FBXLoader;
 
 } )();

+ 156 - 153
examples/js/loaders/GCodeLoader.js

@@ -1,252 +1,255 @@
-/**
- * THREE.GCodeLoader is used to load gcode files usually used for 3D printing or CNC applications.
+( function () {
+
+	/**
+ * GCodeLoader is used to load gcode files usually used for 3D printing or CNC applications.
  *
  * Gcode files are composed by commands used by machines to create objects.
  *
- * @class THREE.GCodeLoader
+ * @class GCodeLoader
  * @param {Manager} manager Loading manager.
  */
 
-THREE.GCodeLoader = function ( manager ) {
-
-	THREE.Loader.call( this, manager );
-
-	this.splitLayer = false;
+	var GCodeLoader = function ( manager ) {
 
-};
+		THREE.Loader.call( this, manager );
+		this.splitLayer = false;
 
-THREE.GCodeLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+	};
 
-	constructor: THREE.GCodeLoader,
+	GCodeLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+		constructor: GCodeLoader,
+		load: function ( url, onLoad, onProgress, onError ) {
 
-	load: function ( url, onLoad, onProgress, onError ) {
+			var scope = this;
+			var loader = new THREE.FileLoader( scope.manager );
+			loader.setPath( scope.path );
+			loader.setRequestHeader( scope.requestHeader );
+			loader.setWithCredentials( scope.withCredentials );
+			loader.load( url, function ( text ) {
 
-		var scope = this;
+				try {
 
-		var loader = new THREE.FileLoader( scope.manager );
-		loader.setPath( scope.path );
-		loader.setRequestHeader( scope.requestHeader );
-		loader.setWithCredentials( scope.withCredentials );
-		loader.load( url, function ( text ) {
+					onLoad( scope.parse( text ) );
 
-			try {
+				} catch ( e ) {
 
-				onLoad( scope.parse( text ) );
+					if ( onError ) {
 
-			} catch ( e ) {
+						onError( e );
 
-				if ( onError ) {
+					} else {
 
-					onError( e );
+						console.error( e );
 
-				} else {
+					}
 
-					console.error( e );
+					scope.manager.itemError( url );
 
 				}
 
-				scope.manager.itemError( url );
-
-			}
+			}, onProgress, onError );
+
+		},
+		parse: function ( data ) {
+
+			var state = {
+				x: 0,
+				y: 0,
+				z: 0,
+				e: 0,
+				f: 0,
+				extruding: false,
+				relative: false
+			};
+			var layers = [];
+			var currentLayer = undefined;
+			var pathMaterial = new THREE.LineBasicMaterial( {
+				color: 0xFF0000
+			} );
+			pathMaterial.name = 'path';
+			var extrudingMaterial = new THREE.LineBasicMaterial( {
+				color: 0x00FF00
+			} );
+			extrudingMaterial.name = 'extruded';
 
-		}, onProgress, onError );
+			function newLayer( line ) {
 
-	},
+				currentLayer = {
+					vertex: [],
+					pathVertex: [],
+					z: line.z
+				};
+				layers.push( currentLayer );
 
-	parse: function ( data ) {
+			} //Create lie segment between p1 and p2
 
-		var state = { x: 0, y: 0, z: 0, e: 0, f: 0, extruding: false, relative: false };
-		var layers = [];
 
-		var currentLayer = undefined;
+			function addSegment( p1, p2 ) {
 
-		var pathMaterial = new THREE.LineBasicMaterial( { color: 0xFF0000 } );
-		pathMaterial.name = 'path';
+				if ( currentLayer === undefined ) {
 
-		var extrudingMaterial = new THREE.LineBasicMaterial( { color: 0x00FF00 } );
-		extrudingMaterial.name = 'extruded';
+					newLayer( p1 );
 
-		function newLayer( line ) {
+				}
 
-			currentLayer = { vertex: [], pathVertex: [], z: line.z };
-			layers.push( currentLayer );
+				if ( line.extruding ) {
 
-		}
+					currentLayer.vertex.push( p1.x, p1.y, p1.z );
+					currentLayer.vertex.push( p2.x, p2.y, p2.z );
 
-		//Create lie segment between p1 and p2
-		function addSegment( p1, p2 ) {
+				} else {
 
-			if ( currentLayer === undefined ) {
+					currentLayer.pathVertex.push( p1.x, p1.y, p1.z );
+					currentLayer.pathVertex.push( p2.x, p2.y, p2.z );
 
-				newLayer( p1 );
+				}
 
 			}
 
-			if ( line.extruding ) {
-
-				currentLayer.vertex.push( p1.x, p1.y, p1.z );
-				currentLayer.vertex.push( p2.x, p2.y, p2.z );
+			function delta( v1, v2 ) {
 
-			} else {
-
-				currentLayer.pathVertex.push( p1.x, p1.y, p1.z );
-				currentLayer.pathVertex.push( p2.x, p2.y, p2.z );
+				return state.relative ? v2 : v2 - v1;
 
 			}
 
-		}
-
-		function delta( v1, v2 ) {
-
-			return state.relative ? v2 : v2 - v1;
-
-		}
+			function absolute( v1, v2 ) {
 
-		function absolute( v1, v2 ) {
+				return state.relative ? v1 + v2 : v2;
 
-			return state.relative ? v1 + v2 : v2;
+			}
 
-		}
+			var lines = data.replace( /;.+/g, '' ).split( '\n' );
 
-		var lines = data.replace( /;.+/g, '' ).split( '\n' );
+			for ( var i = 0; i < lines.length; i ++ ) {
 
-		for ( var i = 0; i < lines.length; i ++ ) {
+				var tokens = lines[ i ].split( ' ' );
+				var cmd = tokens[ 0 ].toUpperCase(); //Argumments
 
-			var tokens = lines[ i ].split( ' ' );
-			var cmd = tokens[ 0 ].toUpperCase();
+				var args = {};
+				tokens.splice( 1 ).forEach( function ( token ) {
 
-			//Argumments
-			var args = {};
-			tokens.splice( 1 ).forEach( function ( token ) {
+					if ( token[ 0 ] !== undefined ) {
 
-				if ( token[ 0 ] !== undefined ) {
+						var key = token[ 0 ].toLowerCase();
+						var value = parseFloat( token.substring( 1 ) );
+						args[ key ] = value;
 
-					var key = token[ 0 ].toLowerCase();
-					var value = parseFloat( token.substring( 1 ) );
-					args[ key ] = value;
+					}
 
-				}
+				} ); //Process commands
+				//G0/G1 – Linear Movement
 
-			} );
+				if ( cmd === 'G0' || cmd === 'G1' ) {
 
-			//Process commands
-			//G0/G1 – Linear Movement
-			if ( cmd === 'G0' || cmd === 'G1' ) {
+					var line = {
+						x: args.x !== undefined ? absolute( state.x, args.x ) : state.x,
+						y: args.y !== undefined ? absolute( state.y, args.y ) : state.y,
+						z: args.z !== undefined ? absolute( state.z, args.z ) : state.z,
+						e: args.e !== undefined ? absolute( state.e, args.e ) : state.e,
+						f: args.f !== undefined ? absolute( state.f, args.f ) : state.f
+					}; //Layer change detection is or made by watching Z, it's made by watching when we extrude at a new Z position
 
-				var line = {
-					x: args.x !== undefined ? absolute( state.x, args.x ) : state.x,
-					y: args.y !== undefined ? absolute( state.y, args.y ) : state.y,
-					z: args.z !== undefined ? absolute( state.z, args.z ) : state.z,
-					e: args.e !== undefined ? absolute( state.e, args.e ) : state.e,
-					f: args.f !== undefined ? absolute( state.f, args.f ) : state.f,
-				};
+					if ( delta( state.e, line.e ) > 0 ) {
 
-				//Layer change detection is or made by watching Z, it's made by watching when we extrude at a new Z position
-				if ( delta( state.e, line.e ) > 0 ) {
+						line.extruding = delta( state.e, line.e ) > 0;
 
-					line.extruding = delta( state.e, line.e ) > 0;
+						if ( currentLayer == undefined || line.z != currentLayer.z ) {
 
-					if ( currentLayer == undefined || line.z != currentLayer.z ) {
+							newLayer( line );
 
-						newLayer( line );
+						}
 
 					}
 
-				}
-
-				addSegment( state, line );
-				state = line;
+					addSegment( state, line );
+					state = line;
 
-			} else if ( cmd === 'G2' || cmd === 'G3' ) {
-
-				//G2/G3 - Arc Movement ( G2 clock wise and G3 counter clock wise )
+				} else if ( cmd === 'G2' || cmd === 'G3' ) { //G2/G3 - Arc Movement ( G2 clock wise and G3 counter clock wise )
 				//console.warn( 'THREE.GCodeLoader: Arc command not supported' );
+				} else if ( cmd === 'G90' ) {
 
-			} else if ( cmd === 'G90' ) {
-
-				//G90: Set to Absolute Positioning
-				state.relative = false;
-
-			} else if ( cmd === 'G91' ) {
+					//G90: Set to Absolute Positioning
+					state.relative = false;
 
-				//G91: Set to state.relative Positioning
-				state.relative = true;
+				} else if ( cmd === 'G91' ) {
 
-			} else if ( cmd === 'G92' ) {
+					//G91: Set to state.relative Positioning
+					state.relative = true;
 
-				//G92: Set Position
-				var line = state;
-				line.x = args.x !== undefined ? args.x : line.x;
-				line.y = args.y !== undefined ? args.y : line.y;
-				line.z = args.z !== undefined ? args.z : line.z;
-				line.e = args.e !== undefined ? args.e : line.e;
-				state = line;
+				} else if ( cmd === 'G92' ) {
 
-			} else {
+					//G92: Set Position
+					var line = state;
+					line.x = args.x !== undefined ? args.x : line.x;
+					line.y = args.y !== undefined ? args.y : line.y;
+					line.z = args.z !== undefined ? args.z : line.z;
+					line.e = args.e !== undefined ? args.e : line.e;
+					state = line;
 
-				//console.warn( 'THREE.GCodeLoader: Command not supported:' + cmd );
+				} else { //console.warn( 'THREE.GCodeLoader: Command not supported:' + cmd );
+				}
 
 			}
 
-		}
-
-		function addObject( vertex, extruding ) {
+			function addObject( vertex, extruding ) {
 
-			var geometry = new THREE.BufferGeometry();
-			geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertex, 3 ) );
+				var geometry = new THREE.BufferGeometry();
+				geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertex, 3 ) );
+				var segments = new THREE.LineSegments( geometry, extruding ? extrudingMaterial : pathMaterial );
+				segments.name = 'layer' + i;
+				object.add( segments );
 
-			var segments = new THREE.LineSegments( geometry, extruding ? extrudingMaterial : pathMaterial );
-			segments.name = 'layer' + i;
-			object.add( segments );
+			}
 
-		}
+			var object = new THREE.Group();
+			object.name = 'gcode';
 
-		var object = new THREE.Group();
-		object.name = 'gcode';
+			if ( this.splitLayer ) {
 
-		if ( this.splitLayer ) {
+				for ( var i = 0; i < layers.length; i ++ ) {
 
-			for ( var i = 0; i < layers.length; i ++ ) {
+					var layer = layers[ i ];
+					addObject( layer.vertex, true );
+					addObject( layer.pathVertex, false );
 
-				var layer = layers[ i ];
-				addObject( layer.vertex, true );
-				addObject( layer.pathVertex, false );
+				}
 
-			}
+			} else {
 
-		} else {
+				var vertex = [],
+					pathVertex = [];
 
-			var vertex = [], pathVertex = [];
+				for ( var i = 0; i < layers.length; i ++ ) {
 
-			for ( var i = 0; i < layers.length; i ++ ) {
+					var layer = layers[ i ];
+					var layerVertex = layer.vertex;
+					var layerPathVertex = layer.pathVertex;
 
-				var layer = layers[ i ];
-				var layerVertex = layer.vertex;
-				var layerPathVertex = layer.pathVertex;
+					for ( var j = 0; j < layerVertex.length; j ++ ) {
 
-				for ( var j = 0; j < layerVertex.length; j ++ ) {
+						vertex.push( layerVertex[ j ] );
 
-					vertex.push( layerVertex[ j ] );
+					}
 
-				}
+					for ( var j = 0; j < layerPathVertex.length; j ++ ) {
 
-				for ( var j = 0; j < layerPathVertex.length; j ++ ) {
+						pathVertex.push( layerPathVertex[ j ] );
 
-					pathVertex.push( layerPathVertex[ j ] );
+					}
 
 				}
 
+				addObject( vertex, true );
+				addObject( pathVertex, false );
+
 			}
 
-			addObject( vertex, true );
-			addObject( pathVertex, false );
+			object.quaternion.setFromEuler( new THREE.Euler( - Math.PI / 2, 0, 0 ) );
+			return object;
 
 		}
+	} );
 
-		object.quaternion.setFromEuler( new THREE.Euler( - Math.PI / 2, 0, 0 ) );
-
-		return object;
-
-	}
+	THREE.GCodeLoader = GCodeLoader;
 
-} );
+} )();

+ 2094 - 2412
examples/js/loaders/GLTFLoader.js

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

+ 60 - 75
examples/js/loaders/HDRCubeTextureLoader.js

@@ -1,95 +1,79 @@
-THREE.HDRCubeTextureLoader = function ( manager ) {
+( function () {
 
-	THREE.Loader.call( this, manager );
+	var HDRCubeTextureLoader = function ( manager ) {
 
-	this.hdrLoader = new THREE.RGBELoader();
-	this.type = THREE.UnsignedByteType;
+		THREE.Loader.call( this, manager );
+		this.hdrLoader = new THREE.RGBELoader();
+		this.type = THREE.UnsignedByteType;
 
-};
+	};
 
-THREE.HDRCubeTextureLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+	HDRCubeTextureLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+		constructor: HDRCubeTextureLoader,
+		load: function ( urls, onLoad, onProgress, onError ) {
 
-	constructor: THREE.HDRCubeTextureLoader,
+			if ( ! Array.isArray( urls ) ) {
 
-	load: function ( urls, onLoad, onProgress, onError ) {
+				console.warn( 'THREE.HDRCubeTextureLoader signature has changed. Use .setDataType() instead.' );
+				this.setDataType( urls );
+				urls = onLoad;
+				onLoad = onProgress;
+				onProgress = onError;
+				onError = arguments[ 4 ];
 
-		if ( ! Array.isArray( urls ) ) {
+			}
 
-			console.warn( 'THREE.HDRCubeTextureLoader signature has changed. Use .setDataType() instead.' );
+			var texture = new THREE.CubeTexture();
+			texture.type = this.type;
 
-			this.setDataType( urls );
+			switch ( texture.type ) {
 
-			urls = onLoad;
-			onLoad = onProgress;
-			onProgress = onError;
-			onError = arguments[ 4 ];
+				case THREE.UnsignedByteType:
+					texture.encoding = THREE.RGBEEncoding;
+					texture.format = THREE.RGBAFormat;
+					texture.minFilter = THREE.NearestFilter;
+					texture.magFilter = THREE.NearestFilter;
+					texture.generateMipmaps = false;
+					break;
 
-		}
-
-		var texture = new THREE.CubeTexture();
-
-		texture.type = this.type;
-
-		switch ( texture.type ) {
-
-			case THREE.UnsignedByteType:
+				case THREE.FloatType:
+					texture.encoding = THREE.LinearEncoding;
+					texture.format = THREE.RGBFormat;
+					texture.minFilter = THREE.LinearFilter;
+					texture.magFilter = THREE.LinearFilter;
+					texture.generateMipmaps = false;
+					break;
 
-				texture.encoding = THREE.RGBEEncoding;
-				texture.format = THREE.RGBAFormat;
-				texture.minFilter = THREE.NearestFilter;
-				texture.magFilter = THREE.NearestFilter;
-				texture.generateMipmaps = false;
-				break;
+				case THREE.HalfFloatType:
+					texture.encoding = THREE.LinearEncoding;
+					texture.format = THREE.RGBFormat;
+					texture.minFilter = THREE.LinearFilter;
+					texture.magFilter = THREE.LinearFilter;
+					texture.generateMipmaps = false;
+					break;
 
-			case THREE.FloatType:
-
-				texture.encoding = THREE.LinearEncoding;
-				texture.format = THREE.RGBFormat;
-				texture.minFilter = THREE.LinearFilter;
-				texture.magFilter = THREE.LinearFilter;
-				texture.generateMipmaps = false;
-				break;
-
-			case THREE.HalfFloatType:
-
-				texture.encoding = THREE.LinearEncoding;
-				texture.format = THREE.RGBFormat;
-				texture.minFilter = THREE.LinearFilter;
-				texture.magFilter = THREE.LinearFilter;
-				texture.generateMipmaps = false;
-				break;
-
-		}
+			}
 
-		var scope = this;
+			var scope = this;
+			var loaded = 0;
 
-		var loaded = 0;
+			function loadHDRData( i, onLoad, onProgress, onError ) {
 
-		function loadHDRData( i, onLoad, onProgress, onError ) {
-
-			new THREE.FileLoader( scope.manager )
-				.setPath( scope.path )
-				.setResponseType( 'arraybuffer' )
-				.setWithCredentials( scope.withCredentials )
-				.load( urls[ i ], function ( buffer ) {
+				new THREE.FileLoader( scope.manager ).setPath( scope.path ).setResponseType( 'arraybuffer' ).setWithCredentials( scope.withCredentials ).load( urls[ i ], function ( buffer ) {
 
 					loaded ++;
-
 					var texData = scope.hdrLoader.parse( buffer );
-
 					if ( ! texData ) return;
 
 					if ( texData.data !== undefined ) {
 
 						var dataTexture = new THREE.DataTexture( texData.data, texData.width, texData.height );
-
 						dataTexture.type = texture.type;
 						dataTexture.encoding = texture.encoding;
 						dataTexture.format = texture.format;
 						dataTexture.minFilter = texture.minFilter;
 						dataTexture.magFilter = texture.magFilter;
 						dataTexture.generateMipmaps = texture.generateMipmaps;
-
 						texture.images[ i ] = dataTexture;
 
 					}
@@ -103,25 +87,26 @@ THREE.HDRCubeTextureLoader.prototype = Object.assign( Object.create( THREE.Loade
 
 				}, onProgress, onError );
 
-		}
-
-		for ( var i = 0; i < urls.length; i ++ ) {
+			}
 
-			loadHDRData( i, onLoad, onProgress, onError );
+			for ( var i = 0; i < urls.length; i ++ ) {
 
-		}
+				loadHDRData( i, onLoad, onProgress, onError );
 
-		return texture;
+			}
 
-	},
+			return texture;
 
-	setDataType: function ( value ) {
+		},
+		setDataType: function ( value ) {
 
-		this.type = value;
-		this.hdrLoader.setDataType( value );
+			this.type = value;
+			this.hdrLoader.setDataType( value );
+			return this;
 
-		return this;
+		}
+	} );
 
-	}
+	THREE.HDRCubeTextureLoader = HDRCubeTextureLoader;
 
-} );
+} )();

+ 67 - 65
examples/js/loaders/KMZLoader.js

@@ -1,121 +1,123 @@
-THREE.KMZLoader = function ( manager ) {
+( function () {
 
-	THREE.Loader.call( this, manager );
+	var KMZLoader = function ( manager ) {
 
-};
+		THREE.Loader.call( this, manager );
 
-THREE.KMZLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+	};
 
-	constructor: THREE.KMZLoader,
+	KMZLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+		constructor: KMZLoader,
+		load: function ( url, onLoad, onProgress, onError ) {
 
-	load: function ( url, onLoad, onProgress, onError ) {
+			var scope = this;
+			var loader = new THREE.FileLoader( scope.manager );
+			loader.setPath( scope.path );
+			loader.setResponseType( 'arraybuffer' );
+			loader.setRequestHeader( scope.requestHeader );
+			loader.setWithCredentials( scope.withCredentials );
+			loader.load( url, function ( text ) {
 
-		var scope = this;
+				try {
 
-		var loader = new THREE.FileLoader( scope.manager );
-		loader.setPath( scope.path );
-		loader.setResponseType( 'arraybuffer' );
-		loader.setRequestHeader( scope.requestHeader );
-		loader.setWithCredentials( scope.withCredentials );
-		loader.load( url, function ( text ) {
+					onLoad( scope.parse( text ) );
 
-			try {
+				} catch ( e ) {
 
-				onLoad( scope.parse( text ) );
+					if ( onError ) {
 
-			} catch ( e ) {
+						onError( e );
 
-				if ( onError ) {
+					} else {
 
-					onError( e );
+						console.error( e );
 
-				} else {
+					}
 
-					console.error( e );
+					scope.manager.itemError( url );
 
 				}
 
-				scope.manager.itemError( url );
+			}, onProgress, onError );
 
-			}
-
-		}, onProgress, onError );
+		},
+		parse: function ( data ) {
 
-	},
+			function findFile( url ) {
 
-	parse: function ( data ) {
+				for ( var path in zip ) {
 
-		function findFile( url ) {
+					if ( path.substr( - url.length ) === url ) {
 
-			for ( var path in zip ) {
+						return zip[ path ];
 
-				if ( path.substr( - url.length ) === url ) {
-
-					return zip[ path ];
+					}
 
 				}
 
 			}
 
-		}
+			var manager = new THREE.LoadingManager();
+			manager.setURLModifier( function ( url ) {
 
-		var manager = new THREE.LoadingManager();
-		manager.setURLModifier( function ( url ) {
+				var image = findFile( url );
 
-			var image = findFile( url );
+				if ( image ) {
 
-			if ( image ) {
+					console.log( 'Loading', url );
+					var blob = new Blob( [ image.buffer ], {
+						type: 'application/octet-stream'
+					} );
+					return URL.createObjectURL( blob );
 
-				console.log( 'Loading', url );
-
-				var blob = new Blob( [ image.buffer ], { type: 'application/octet-stream' } );
-				return URL.createObjectURL( blob );
-
-			}
+				}
 
-			return url;
+				return url;
 
-		} );
+			} ); //
 
-		//
+			var zip = fflate.unzipSync( new Uint8Array( data ) ); // eslint-disable-line no-undef
 
-		var zip = fflate.unzipSync( new Uint8Array( data ) ); // eslint-disable-line no-undef
+			if ( zip[ 'doc.kml' ] ) {
 
-		if ( zip[ 'doc.kml' ] ) {
+				var xml = new DOMParser().parseFromString( fflate.strFromU8( zip[ 'doc.kml' ] ), 'application/xml' ); // eslint-disable-line no-undef
 
-			var xml = new DOMParser().parseFromString( fflate.strFromU8( zip[ 'doc.kml' ] ), 'application/xml' ); // eslint-disable-line no-undef
+				var model = xml.querySelector( 'Placemark Model Link href' );
 
-			var model = xml.querySelector( 'Placemark Model Link href' );
+				if ( model ) {
 
-			if ( model ) {
+					var loader = new THREE.ColladaLoader( manager );
+					return loader.parse( fflate.strFromU8( zip[ model.textContent ] ) ); // eslint-disable-line no-undef
 
-				var loader = new THREE.ColladaLoader( manager );
-				return loader.parse( fflate.strFromU8( zip[ model.textContent ] ) ); // eslint-disable-line no-undef
+				}
 
-			}
+			} else {
 
-		} else {
+				console.warn( 'KMZLoader: Missing doc.kml file.' );
 
-			console.warn( 'KMZLoader: Missing doc.kml file.' );
+				for ( var path in zip ) {
 
-			for ( var path in zip ) {
+					var extension = path.split( '.' ).pop().toLowerCase();
 
-				var extension = path.split( '.' ).pop().toLowerCase();
+					if ( extension === 'dae' ) {
 
-				if ( extension === 'dae' ) {
+						var loader = new THREE.ColladaLoader( manager );
+						return loader.parse( fflate.strFromU8( zip[ path ] ) ); // eslint-disable-line no-undef
 
-					var loader = new THREE.ColladaLoader( manager );
-					return loader.parse( fflate.strFromU8( zip[ path ] ) ); // eslint-disable-line no-undef
+					}
 
 				}
 
 			}
 
-		}
+			console.error( 'KMZLoader: Couldn\'t find .dae file.' );
+			return {
+				scene: new THREE.Group()
+			};
 
-		console.error( 'KMZLoader: Couldn\'t find .dae file.' );
-		return { scene: new THREE.Group() };
+		}
+	} );
 
-	}
+	THREE.KMZLoader = KMZLoader;
 
-} );
+} )();

+ 127 - 119
examples/js/loaders/KTXLoader.js

@@ -1,174 +1,182 @@
-/**
+( function () {
+
+	/**
  * for description see https://www.khronos.org/opengles/sdk/tools/KTX/
  * for file layout see https://www.khronos.org/opengles/sdk/tools/KTX/file_format_spec/
  *
  * ported from https://github.com/BabylonJS/Babylon.js/blob/master/src/Tools/babylon.khronosTextureContainer.ts
  */
 
+	var KTXLoader = function ( manager ) {
 
-THREE.KTXLoader = function ( manager ) {
-
-	THREE.CompressedTextureLoader.call( this, manager );
-
-};
-
-THREE.KTXLoader.prototype = Object.assign( Object.create( THREE.CompressedTextureLoader.prototype ), {
-
-	constructor: THREE.KTXLoader,
+		THREE.CompressedTextureLoader.call( this, manager );
 
-	parse: function ( buffer, loadMipmaps ) {
-
-		var ktx = new KhronosTextureContainer( buffer, 1 );
+	};
 
-		return {
-			mipmaps: ktx.mipmaps( loadMipmaps ),
-			width: ktx.pixelWidth,
-			height: ktx.pixelHeight,
-			format: ktx.glInternalFormat,
-			isCubemap: ktx.numberOfFaces === 6,
-			mipmapCount: ktx.numberOfMipmapLevels
-		};
+	KTXLoader.prototype = Object.assign( Object.create( THREE.CompressedTextureLoader.prototype ), {
+		constructor: KTXLoader,
+		parse: function ( buffer, loadMipmaps ) {
 
-	}
+			var ktx = new KhronosTextureContainer( buffer, 1 );
+			return {
+				mipmaps: ktx.mipmaps( loadMipmaps ),
+				width: ktx.pixelWidth,
+				height: ktx.pixelHeight,
+				format: ktx.glInternalFormat,
+				isCubemap: ktx.numberOfFaces === 6,
+				mipmapCount: ktx.numberOfMipmapLevels
+			};
 
-} );
+		}
+	} );
 
-var KhronosTextureContainer = ( function () {
+	var KhronosTextureContainer = function () {
 
-	/**
+		/**
 	 * @param {ArrayBuffer} arrayBuffer- contents of the KTX container file
 	 * @param {number} facesExpected- should be either 1 or 6, based whether a cube texture or or
 	 * @param {boolean} threeDExpected- provision for indicating that data should be a 3D texture, not implemented
 	 * @param {boolean} textureArrayExpected- provision for indicating that data should be a texture array, not implemented
 	 */
-	function KhronosTextureContainer( arrayBuffer, facesExpected /*, threeDExpected, textureArrayExpected */ ) {
-
-		this.arrayBuffer = arrayBuffer;
-
-		// Test that it is a ktx formatted file, based on the first 12 bytes, character representation is:
-		// '´', 'K', 'T', 'X', ' ', '1', '1', 'ª', '\r', '\n', '\x1A', '\n'
-		// 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A
-		var identifier = new Uint8Array( this.arrayBuffer, 0, 12 );
-		if ( identifier[ 0 ] !== 0xAB ||
-			identifier[ 1 ] !== 0x4B ||
-			identifier[ 2 ] !== 0x54 ||
-			identifier[ 3 ] !== 0x58 ||
-			identifier[ 4 ] !== 0x20 ||
-			identifier[ 5 ] !== 0x31 ||
-			identifier[ 6 ] !== 0x31 ||
-			identifier[ 7 ] !== 0xBB ||
-			identifier[ 8 ] !== 0x0D ||
-			identifier[ 9 ] !== 0x0A ||
-			identifier[ 10 ] !== 0x1A ||
-			identifier[ 11 ] !== 0x0A ) {
-
-			console.error( 'texture missing KTX identifier' );
-			return;
+		function KhronosTextureContainer( arrayBuffer, facesExpected
+			/*, threeDExpected, textureArrayExpected */
+		) {
 
-		}
+			this.arrayBuffer = arrayBuffer; // Test that it is a ktx formatted file, based on the first 12 bytes, character representation is:
+			// '´', 'K', 'T', 'X', ' ', '1', '1', 'ª', '\r', '\n', '\x1A', '\n'
+			// 0xAB, 0x4B, 0x54, 0x58, 0x20, 0x31, 0x31, 0xBB, 0x0D, 0x0A, 0x1A, 0x0A
 
-		// load the reset of the header in native 32 bit uint
-		var dataSize = Uint32Array.BYTES_PER_ELEMENT;
-		var headerDataView = new DataView( this.arrayBuffer, 12, 13 * dataSize );
-		var endianness = headerDataView.getUint32( 0, true );
-		var littleEndian = endianness === 0x04030201;
+			var identifier = new Uint8Array( this.arrayBuffer, 0, 12 );
 
-		this.glType = headerDataView.getUint32( 1 * dataSize, littleEndian ); // must be 0 for compressed textures
-		this.glTypeSize = headerDataView.getUint32( 2 * dataSize, littleEndian ); // must be 1 for compressed textures
-		this.glFormat = headerDataView.getUint32( 3 * dataSize, littleEndian ); // must be 0 for compressed textures
-		this.glInternalFormat = headerDataView.getUint32( 4 * dataSize, littleEndian ); // the value of arg passed to gl.compressedTexImage2D(,,x,,,,)
-		this.glBaseInternalFormat = headerDataView.getUint32( 5 * dataSize, littleEndian ); // specify GL_RGB, GL_RGBA, GL_ALPHA, etc (un-compressed only)
-		this.pixelWidth = headerDataView.getUint32( 6 * dataSize, littleEndian ); // level 0 value of arg passed to gl.compressedTexImage2D(,,,x,,,)
-		this.pixelHeight = headerDataView.getUint32( 7 * dataSize, littleEndian ); // level 0 value of arg passed to gl.compressedTexImage2D(,,,,x,,)
-		this.pixelDepth = headerDataView.getUint32( 8 * dataSize, littleEndian ); // level 0 value of arg passed to gl.compressedTexImage3D(,,,,,x,,)
-		this.numberOfArrayElements = headerDataView.getUint32( 9 * dataSize, littleEndian ); // used for texture arrays
-		this.numberOfFaces = headerDataView.getUint32( 10 * dataSize, littleEndian ); // used for cubemap textures, should either be 1 or 6
-		this.numberOfMipmapLevels = headerDataView.getUint32( 11 * dataSize, littleEndian ); // number of levels; disregard possibility of 0 for compressed textures
-		this.bytesOfKeyValueData = headerDataView.getUint32( 12 * dataSize, littleEndian ); // the amount of space after the header for meta-data
+			if ( identifier[ 0 ] !== 0xAB || identifier[ 1 ] !== 0x4B || identifier[ 2 ] !== 0x54 || identifier[ 3 ] !== 0x58 || identifier[ 4 ] !== 0x20 || identifier[ 5 ] !== 0x31 || identifier[ 6 ] !== 0x31 || identifier[ 7 ] !== 0xBB || identifier[ 8 ] !== 0x0D || identifier[ 9 ] !== 0x0A || identifier[ 10 ] !== 0x1A || identifier[ 11 ] !== 0x0A ) {
 
-		// Make sure we have a compressed type.  Not only reduces work, but probably better to let dev know they are not compressing.
-		if ( this.glType !== 0 ) {
+				console.error( 'texture missing KTX identifier' );
+				return;
 
-			console.warn( 'only compressed formats currently supported' );
-			return;
+			} // load the reset of the header in native 32 bit uint
 
-		} else {
 
-			// value of zero is an indication to generate mipmaps @ runtime.  Not usually allowed for compressed, so disregard.
-			this.numberOfMipmapLevels = Math.max( 1, this.numberOfMipmapLevels );
+			var dataSize = Uint32Array.BYTES_PER_ELEMENT;
+			var headerDataView = new DataView( this.arrayBuffer, 12, 13 * dataSize );
+			var endianness = headerDataView.getUint32( 0, true );
+			var littleEndian = endianness === 0x04030201;
+			this.glType = headerDataView.getUint32( 1 * dataSize, littleEndian ); // must be 0 for compressed textures
 
-		}
+			this.glTypeSize = headerDataView.getUint32( 2 * dataSize, littleEndian ); // must be 1 for compressed textures
 
-		if ( this.pixelHeight === 0 || this.pixelDepth !== 0 ) {
+			this.glFormat = headerDataView.getUint32( 3 * dataSize, littleEndian ); // must be 0 for compressed textures
 
-			console.warn( 'only 2D textures currently supported' );
-			return;
+			this.glInternalFormat = headerDataView.getUint32( 4 * dataSize, littleEndian ); // the value of arg passed to gl.compressedTexImage2D(,,x,,,,)
 
-		}
+			this.glBaseInternalFormat = headerDataView.getUint32( 5 * dataSize, littleEndian ); // specify GL_RGB, GL_RGBA, GL_ALPHA, etc (un-compressed only)
 
-		if ( this.numberOfArrayElements !== 0 ) {
+			this.pixelWidth = headerDataView.getUint32( 6 * dataSize, littleEndian ); // level 0 value of arg passed to gl.compressedTexImage2D(,,,x,,,)
 
-			console.warn( 'texture arrays not currently supported' );
-			return;
+			this.pixelHeight = headerDataView.getUint32( 7 * dataSize, littleEndian ); // level 0 value of arg passed to gl.compressedTexImage2D(,,,,x,,)
 
-		}
+			this.pixelDepth = headerDataView.getUint32( 8 * dataSize, littleEndian ); // level 0 value of arg passed to gl.compressedTexImage3D(,,,,,x,,)
 
-		if ( this.numberOfFaces !== facesExpected ) {
+			this.numberOfArrayElements = headerDataView.getUint32( 9 * dataSize, littleEndian ); // used for texture arrays
 
-			console.warn( 'number of faces expected' + facesExpected + ', but found ' + this.numberOfFaces );
-			return;
+			this.numberOfFaces = headerDataView.getUint32( 10 * dataSize, littleEndian ); // used for cubemap textures, should either be 1 or 6
 
-		}
+			this.numberOfMipmapLevels = headerDataView.getUint32( 11 * dataSize, littleEndian ); // number of levels; disregard possibility of 0 for compressed textures
 
-		// we now have a completely validated file, so could use existence of loadType as success
-		// would need to make this more elaborate & adjust checks above to support more than one load type
-		this.loadType = KhronosTextureContainer.COMPRESSED_2D;
+			this.bytesOfKeyValueData = headerDataView.getUint32( 12 * dataSize, littleEndian ); // the amount of space after the header for meta-data
+			// Make sure we have a compressed type.	Not only reduces work, but probably better to let dev know they are not compressing.
 
-	}
+			if ( this.glType !== 0 ) {
 
-	// return mipmaps for THREE.js
-	KhronosTextureContainer.prototype.mipmaps = function ( loadMipmaps ) {
+				console.warn( 'only compressed formats currently supported' );
+				return;
 
-		var mipmaps = [];
+			} else {
 
-		// initialize width & height for level 1
-		var dataOffset = KhronosTextureContainer.HEADER_LEN + this.bytesOfKeyValueData;
-		var width = this.pixelWidth;
-		var height = this.pixelHeight;
-		var mipmapCount = loadMipmaps ? this.numberOfMipmapLevels : 1;
+				// value of zero is an indication to generate mipmaps @ runtime.	Not usually allowed for compressed, so disregard.
+				this.numberOfMipmapLevels = Math.max( 1, this.numberOfMipmapLevels );
 
-		for ( var level = 0; level < mipmapCount; level ++ ) {
+			}
 
-			var imageSize = new Int32Array( this.arrayBuffer, dataOffset, 1 )[ 0 ]; // size per face, since not supporting array cubemaps
-			dataOffset += 4; // size of the image + 4 for the imageSize field
+			if ( this.pixelHeight === 0 || this.pixelDepth !== 0 ) {
 
-			for ( var face = 0; face < this.numberOfFaces; face ++ ) {
+				console.warn( 'only 2D textures currently supported' );
+				return;
 
-				var byteArray = new Uint8Array( this.arrayBuffer, dataOffset, imageSize );
+			}
 
-				mipmaps.push( { 'data': byteArray, 'width': width, 'height': height } );
+			if ( this.numberOfArrayElements !== 0 ) {
 
-				dataOffset += imageSize;
-				dataOffset += 3 - ( ( imageSize + 3 ) % 4 ); // add padding for odd sized image
+				console.warn( 'texture arrays not currently supported' );
+				return;
 
 			}
 
-			width = Math.max( 1.0, width * 0.5 );
-			height = Math.max( 1.0, height * 0.5 );
+			if ( this.numberOfFaces !== facesExpected ) {
 
-		}
+				console.warn( 'number of faces expected' + facesExpected + ', but found ' + this.numberOfFaces );
+				return;
 
-		return mipmaps;
+			} // we now have a completely validated file, so could use existence of loadType as success
+			// would need to make this more elaborate & adjust checks above to support more than one load type
 
-	};
 
-	KhronosTextureContainer.HEADER_LEN = 12 + ( 13 * 4 ); // identifier + header elements (not including key value meta-data pairs)
-	// load types
-	KhronosTextureContainer.COMPRESSED_2D = 0; // uses a gl.compressedTexImage2D()
-	KhronosTextureContainer.COMPRESSED_3D = 1; // uses a gl.compressedTexImage3D()
-	KhronosTextureContainer.TEX_2D = 2; // uses a gl.texImage2D()
-	KhronosTextureContainer.TEX_3D = 3; // uses a gl.texImage3D()
+			this.loadType = KhronosTextureContainer.COMPRESSED_2D;
+
+		} // return mipmaps for js
+
+
+		KhronosTextureContainer.prototype.mipmaps = function ( loadMipmaps ) {
+
+			var mipmaps = []; // initialize width & height for level 1
+
+			var dataOffset = KhronosTextureContainer.HEADER_LEN + this.bytesOfKeyValueData;
+			var width = this.pixelWidth;
+			var height = this.pixelHeight;
+			var mipmapCount = loadMipmaps ? this.numberOfMipmapLevels : 1;
+
+			for ( var level = 0; level < mipmapCount; level ++ ) {
+
+				var imageSize = new Int32Array( this.arrayBuffer, dataOffset, 1 )[ 0 ]; // size per face, since not supporting array cubemaps
+
+				dataOffset += 4; // size of the image + 4 for the imageSize field
+
+				for ( var face = 0; face < this.numberOfFaces; face ++ ) {
+
+					var byteArray = new Uint8Array( this.arrayBuffer, dataOffset, imageSize );
+					mipmaps.push( {
+						'data': byteArray,
+						'width': width,
+						'height': height
+					} );
+					dataOffset += imageSize;
+					dataOffset += 3 - ( imageSize + 3 ) % 4; // add padding for odd sized image
+
+				}
+
+				width = Math.max( 1.0, width * 0.5 );
+				height = Math.max( 1.0, height * 0.5 );
+
+			}
+
+			return mipmaps;
+
+		};
+
+		KhronosTextureContainer.HEADER_LEN = 12 + 13 * 4; // identifier + header elements (not including key value meta-data pairs)
+		// load types
+
+		KhronosTextureContainer.COMPRESSED_2D = 0; // uses a gl.compressedTexImage2D()
+
+		KhronosTextureContainer.COMPRESSED_3D = 1; // uses a gl.compressedTexImage3D()
+
+		KhronosTextureContainer.TEX_2D = 2; // uses a gl.texImage2D()
+
+		KhronosTextureContainer.TEX_3D = 3; // uses a gl.texImage3D()
+
+		return KhronosTextureContainer;
+
+	}();
 
-	return KhronosTextureContainer;
+	THREE.KTXLoader = KTXLoader;
 
-}() );
+} )();

+ 1119 - 1261
examples/js/loaders/LDrawLoader.js

@@ -1,6 +1,10 @@
-THREE.LDrawLoader = ( function () {
+( function () {
 
-	var conditionalLineVertShader = /* glsl */`
+	var LDrawLoader = function () {
+
+		var conditionalLineVertShader =
+	/* glsl */
+	`
 	attribute vec3 control0;
 	attribute vec3 control1;
 	attribute vec3 direction;
@@ -47,8 +51,9 @@ THREE.LDrawLoader = ( function () {
 		#include <fog_vertex>
 	}
 	`;
-
-	var conditionalLineFragShader = /* glsl */`
+		var conditionalLineFragShader =
+	/* glsl */
+	`
 	uniform vec3 diffuse;
 	uniform float opacity;
 	varying float discardFlag;
@@ -75,1876 +80,1729 @@ THREE.LDrawLoader = ( function () {
 		#include <premultiplied_alpha_fragment>
 	}
 	`;
+		var tempVec0 = new THREE.Vector3();
+		var tempVec1 = new THREE.Vector3();
 
+		function smoothNormals( triangles, lineSegments ) {
 
+			function hashVertex( v ) {
 
-	var tempVec0 = new THREE.Vector3();
-	var tempVec1 = new THREE.Vector3();
-	function smoothNormals( triangles, lineSegments ) {
-
-		function hashVertex( v ) {
-
-			// NOTE: 1e2 is pretty coarse but was chosen because it allows edges
-			// to be smoothed as expected (see minifig arms). The errors between edges
-			// could be due to matrix multiplication.
-			var x = ~ ~ ( v.x * 1e2 );
-			var y = ~ ~ ( v.y * 1e2 );
-			var z = ~ ~ ( v.z * 1e2 );
-			return `${ x },${ y },${ z }`;
-
-		}
-
-		function hashEdge( v0, v1 ) {
-
-			return `${ hashVertex( v0 ) }_${ hashVertex( v1 ) }`;
-
-		}
-
-		var hardEdges = new Set();
-		var halfEdgeList = {};
-		var fullHalfEdgeList = {};
-		var normals = [];
-
-		// Save the list of hard edges by hash
-		for ( var i = 0, l = lineSegments.length; i < l; i ++ ) {
-
-			var ls = lineSegments[ i ];
-			var v0 = ls.v0;
-			var v1 = ls.v1;
-			hardEdges.add( hashEdge( v0, v1 ) );
-			hardEdges.add( hashEdge( v1, v0 ) );
-
-		}
-
-		// track the half edges associated with each triangle
-		for ( var i = 0, l = triangles.length; i < l; i ++ ) {
-
-			var tri = triangles[ i ];
-			for ( var i2 = 0, l2 = 3; i2 < l2; i2 ++ ) {
-
-				var index = i2;
-				var next = ( i2 + 1 ) % 3;
-				var v0 = tri[ `v${ index }` ];
-				var v1 = tri[ `v${ next }` ];
-				var hash = hashEdge( v0, v1 );
-
-				// don't add the triangle if the edge is supposed to be hard
-				if ( hardEdges.has( hash ) ) continue;
-				halfEdgeList[ hash ] = tri;
-				fullHalfEdgeList[ hash ] = tri;
+				// NOTE: 1e2 is pretty coarse but was chosen because it allows edges
+				// to be smoothed as expected (see minifig arms). The errors between edges
+				// could be due to matrix multiplication.
+				var x = ~ ~ ( v.x * 1e2 );
+				var y = ~ ~ ( v.y * 1e2 );
+				var z = ~ ~ ( v.z * 1e2 );
+				return `${x},${y},${z}`;
 
 			}
 
-		}
+			function hashEdge( v0, v1 ) {
 
-		// NOTE: Some of the normals wind up being skewed in an unexpected way because
-		// quads provide more "influence" to some vertex normals than a triangle due to
-		// the fact that a quad is made up of two triangles and all triangles are weighted
-		// equally. To fix this quads could be tracked separately so their vertex normals
-		// are weighted appropriately or we could try only adding a normal direction
-		// once per normal.
+				return `${hashVertex( v0 )}_${hashVertex( v1 )}`;
 
-		// Iterate until we've tried to connect all triangles to share normals
-		while ( true ) {
-
-			// Stop if there are no more triangles left
-			var halfEdges = Object.keys( halfEdgeList );
-			if ( halfEdges.length === 0 ) break;
-
-			// Exhaustively find all connected triangles
-			var i = 0;
-			var queue = [ fullHalfEdgeList[ halfEdges[ 0 ] ] ];
-			while ( i < queue.length ) {
-
-				// initialize all vertex normals in this triangle
-				var tri = queue[ i ];
-				i ++;
-
-				var faceNormal = tri.faceNormal;
-				if ( tri.n0 === null ) {
-
-					tri.n0 = faceNormal.clone();
-					normals.push( tri.n0 );
+			}
 
-				}
+			var hardEdges = new Set();
+			var halfEdgeList = {};
+			var fullHalfEdgeList = {};
+			var normals = []; // Save the list of hard edges by hash
 
-				if ( tri.n1 === null ) {
+			for ( var i = 0, l = lineSegments.length; i < l; i ++ ) {
 
-					tri.n1 = faceNormal.clone();
-					normals.push( tri.n1 );
+				var ls = lineSegments[ i ];
+				var v0 = ls.v0;
+				var v1 = ls.v1;
+				hardEdges.add( hashEdge( v0, v1 ) );
+				hardEdges.add( hashEdge( v1, v0 ) );
 
-				}
+			} // track the half edges associated with each triangle
 
-				if ( tri.n2 === null ) {
 
-					tri.n2 = faceNormal.clone();
-					normals.push( tri.n2 );
+			for ( var i = 0, l = triangles.length; i < l; i ++ ) {
 
-				}
+				var tri = triangles[ i ];
 
-				// Check if any edge is connected to another triangle edge
 				for ( var i2 = 0, l2 = 3; i2 < l2; i2 ++ ) {
 
 					var index = i2;
 					var next = ( i2 + 1 ) % 3;
-					var v0 = tri[ `v${ index }` ];
-					var v1 = tri[ `v${ next }` ];
-
-					// delete this triangle from the list so it won't be found again
-					var hash = hashEdge( v0, v1 );
-					delete halfEdgeList[ hash ];
+					var v0 = tri[ `v${index}` ];
+					var v1 = tri[ `v${next}` ];
+					var hash = hashEdge( v0, v1 ); // don't add the triangle if the edge is supposed to be hard
 
-					var reverseHash = hashEdge( v1, v0 );
-					var otherTri = fullHalfEdgeList[ reverseHash ];
-					if ( otherTri ) {
+					if ( hardEdges.has( hash ) ) continue;
+					halfEdgeList[ hash ] = tri;
+					fullHalfEdgeList[ hash ] = tri;
 
-						// NOTE: If the angle between triangles is > 67.5 degrees then assume it's
-						// hard edge. There are some cases where the line segments do not line up exactly
-						// with or span multiple triangle edges (see Lunar Vehicle wheels).
-						if ( Math.abs( otherTri.faceNormal.dot( tri.faceNormal ) ) < 0.25 ) {
-
-							continue;
-
-						}
-
-						// if this triangle has already been traversed then it won't be in
-						// the halfEdgeList. If it has not then add it to the queue and delete
-						// it so it won't be found again.
-						if ( reverseHash in halfEdgeList ) {
-
-							queue.push( otherTri );
-							delete halfEdgeList[ reverseHash ];
-
-						}
+				}
 
-						// Find the matching edge in this triangle and copy the normal vector over
-						for ( var i3 = 0, l3 = 3; i3 < l3; i3 ++ ) {
+			} // NOTE: Some of the normals wind up being skewed in an unexpected way because
+			// quads provide more "influence" to some vertex normals than a triangle due to
+			// the fact that a quad is made up of two triangles and all triangles are weighted
+			// equally. To fix this quads could be tracked separately so their vertex normals
+			// are weighted appropriately or we could try only adding a normal direction
+			// once per normal.
+			// Iterate until we've tried to connect all triangles to share normals
 
-							var otherIndex = i3;
-							var otherNext = ( i3 + 1 ) % 3;
-							var otherV0 = otherTri[ `v${ otherIndex }` ];
-							var otherV1 = otherTri[ `v${ otherNext }` ];
 
-							var otherHash = hashEdge( otherV0, otherV1 );
-							if ( otherHash === reverseHash ) {
+			while ( true ) {
 
-								if ( otherTri[ `n${ otherIndex }` ] === null ) {
+				// Stop if there are no more triangles left
+				var halfEdges = Object.keys( halfEdgeList );
+				if ( halfEdges.length === 0 ) break; // Exhaustively find all connected triangles
 
-									var norm = tri[ `n${ next }` ];
-									otherTri[ `n${ otherIndex }` ] = norm;
-									norm.add( otherTri.faceNormal );
+				var i = 0;
+				var queue = [ fullHalfEdgeList[ halfEdges[ 0 ] ] ];
 
-								}
+				while ( i < queue.length ) {
 
-								if ( otherTri[ `n${ otherNext }` ] === null ) {
+					// initialize all vertex normals in this triangle
+					var tri = queue[ i ];
+					i ++;
+					var faceNormal = tri.faceNormal;
 
-									var norm = tri[ `n${ index }` ];
-									otherTri[ `n${ otherNext }` ] = norm;
-									norm.add( otherTri.faceNormal );
+					if ( tri.n0 === null ) {
 
-								}
+						tri.n0 = faceNormal.clone();
+						normals.push( tri.n0 );
 
-								break;
+					}
 
-							}
+					if ( tri.n1 === null ) {
 
-						}
+						tri.n1 = faceNormal.clone();
+						normals.push( tri.n1 );
 
 					}
 
-				}
+					if ( tri.n2 === null ) {
 
-			}
+						tri.n2 = faceNormal.clone();
+						normals.push( tri.n2 );
 
-		}
+					} // Check if any edge is connected to another triangle edge
 
-		// The normals of each face have been added up so now we average them by normalizing the vector.
-		for ( var i = 0, l = normals.length; i < l; i ++ ) {
 
-			normals[ i ].normalize();
+					for ( var i2 = 0, l2 = 3; i2 < l2; i2 ++ ) {
 
-		}
+						var index = i2;
+						var next = ( i2 + 1 ) % 3;
+						var v0 = tri[ `v${index}` ];
+						var v1 = tri[ `v${next}` ]; // delete this triangle from the list so it won't be found again
 
-	}
+						var hash = hashEdge( v0, v1 );
+						delete halfEdgeList[ hash ];
+						var reverseHash = hashEdge( v1, v0 );
+						var otherTri = fullHalfEdgeList[ reverseHash ];
 
-	function isPrimitiveType( type ) {
+						if ( otherTri ) {
 
-		return /primitive/i.test( type ) || type === 'Subpart';
+							// NOTE: If the angle between triangles is > 67.5 degrees then assume it's
+							// hard edge. There are some cases where the line segments do not line up exactly
+							// with or span multiple triangle edges (see Lunar Vehicle wheels).
+							if ( Math.abs( otherTri.faceNormal.dot( tri.faceNormal ) ) < 0.25 ) {
 
-	}
+								continue;
 
-	function LineParser( line, lineNumber ) {
+							} // if this triangle has already been traversed then it won't be in
+							// the halfEdgeList. If it has not then add it to the queue and delete
+							// it so it won't be found again.
 
-		this.line = line;
-		this.lineLength = line.length;
-		this.currentCharIndex = 0;
-		this.currentChar = ' ';
-		this.lineNumber = lineNumber;
 
-	}
+							if ( reverseHash in halfEdgeList ) {
 
-	LineParser.prototype = {
+								queue.push( otherTri );
+								delete halfEdgeList[ reverseHash ];
 
-		constructor: LineParser,
+							} // Find the matching edge in this triangle and copy the normal vector over
 
-		seekNonSpace: function () {
 
-			while ( this.currentCharIndex < this.lineLength ) {
+							for ( var i3 = 0, l3 = 3; i3 < l3; i3 ++ ) {
 
-				this.currentChar = this.line.charAt( this.currentCharIndex );
+								var otherIndex = i3;
+								var otherNext = ( i3 + 1 ) % 3;
+								var otherV0 = otherTri[ `v${otherIndex}` ];
+								var otherV1 = otherTri[ `v${otherNext}` ];
+								var otherHash = hashEdge( otherV0, otherV1 );
 
-				if ( this.currentChar !== ' ' && this.currentChar !== '\t' ) {
+								if ( otherHash === reverseHash ) {
 
-					return;
+									if ( otherTri[ `n${otherIndex}` ] === null ) {
 
-				}
+										var norm = tri[ `n${next}` ];
+										otherTri[ `n${otherIndex}` ] = norm;
+										norm.add( otherTri.faceNormal );
 
-				this.currentCharIndex ++;
+									}
 
-			}
+									if ( otherTri[ `n${otherNext}` ] === null ) {
 
-		},
+										var norm = tri[ `n${index}` ];
+										otherTri[ `n${otherNext}` ] = norm;
+										norm.add( otherTri.faceNormal );
 
-		getToken: function () {
+									}
 
-			var pos0 = this.currentCharIndex ++;
+									break;
 
-			// Seek space
-			while ( this.currentCharIndex < this.lineLength ) {
+								}
 
-				this.currentChar = this.line.charAt( this.currentCharIndex );
+							}
 
-				if ( this.currentChar === ' ' || this.currentChar === '\t' ) {
+						}
 
-					break;
+					}
 
 				}
 
-				this.currentCharIndex ++;
-
-			}
-
-			var pos1 = this.currentCharIndex;
-
-			this.seekNonSpace();
-
-			return this.line.substring( pos0, pos1 );
+			} // The normals of each face have been added up so now we average them by normalizing the vector.
 
-		},
 
-		getRemainingString: function () {
+			for ( var i = 0, l = normals.length; i < l; i ++ ) {
 
-			return this.line.substring( this.currentCharIndex, this.lineLength );
+				normals[ i ].normalize();
 
-		},
-
-		isAtTheEnd: function () {
-
-			return this.currentCharIndex >= this.lineLength;
-
-		},
-
-		setToEnd: function () {
-
-			this.currentCharIndex = this.lineLength;
-
-		},
-
-		getLineNumberString: function () {
-
-			return this.lineNumber >= 0 ? ' at line ' + this.lineNumber : '';
+			}
 
 		}
 
+		function isPrimitiveType( type ) {
 
-	};
-
-	function sortByMaterial( a, b ) {
-
-		if ( a.colourCode === b.colourCode ) {
-
-			return 0;
+			return /primitive/i.test( type ) || type === 'Subpart';
 
 		}
 
-		if ( a.colourCode < b.colourCode ) {
+		function LineParser( line, lineNumber ) {
 
-			return - 1;
+			this.line = line;
+			this.lineLength = line.length;
+			this.currentCharIndex = 0;
+			this.currentChar = ' ';
+			this.lineNumber = lineNumber;
 
 		}
 
-		return 1;
+		LineParser.prototype = {
+			constructor: LineParser,
+			seekNonSpace: function () {
 
-	}
+				while ( this.currentCharIndex < this.lineLength ) {
 
-	function createObject( elements, elementSize, isConditionalSegments ) {
+					this.currentChar = this.line.charAt( this.currentCharIndex );
 
-		// Creates a THREE.LineSegments (elementSize = 2) or a THREE.Mesh (elementSize = 3 )
-		// With per face / segment material, implemented with mesh groups and materials array
+					if ( this.currentChar !== ' ' && this.currentChar !== '\t' ) {
 
-		// Sort the triangles or line segments by colour code to make later the mesh groups
-		elements.sort( sortByMaterial );
+						return;
 
-		var positions = [];
-		var normals = [];
-		var materials = [];
+					}
 
-		var bufferGeometry = new THREE.BufferGeometry();
-		var prevMaterial = null;
-		var index0 = 0;
-		var numGroupVerts = 0;
+					this.currentCharIndex ++;
 
-		for ( var iElem = 0, nElem = elements.length; iElem < nElem; iElem ++ ) {
+				}
 
-			var elem = elements[ iElem ];
-			var v0 = elem.v0;
-			var v1 = elem.v1;
-			// Note that LDraw coordinate system is rotated 180 deg. in the X axis w.r.t. Three.js's one
-			positions.push( v0.x, v0.y, v0.z, v1.x, v1.y, v1.z );
-			if ( elementSize === 3 ) {
+			},
+			getToken: function () {
 
-				positions.push( elem.v2.x, elem.v2.y, elem.v2.z );
+				var pos0 = this.currentCharIndex ++; // Seek space
 
-				var n0 = elem.n0 || elem.faceNormal;
-				var n1 = elem.n1 || elem.faceNormal;
-				var n2 = elem.n2 || elem.faceNormal;
-				normals.push( n0.x, n0.y, n0.z );
-				normals.push( n1.x, n1.y, n1.z );
-				normals.push( n2.x, n2.y, n2.z );
+				while ( this.currentCharIndex < this.lineLength ) {
 
-			}
+					this.currentChar = this.line.charAt( this.currentCharIndex );
 
-			if ( prevMaterial !== elem.material ) {
+					if ( this.currentChar === ' ' || this.currentChar === '\t' ) {
 
-				if ( prevMaterial !== null ) {
+						break;
 
-					bufferGeometry.addGroup( index0, numGroupVerts, materials.length - 1 );
+					}
+
+					this.currentCharIndex ++;
 
 				}
 
-				materials.push( elem.material );
+				var pos1 = this.currentCharIndex;
+				this.seekNonSpace();
+				return this.line.substring( pos0, pos1 );
 
-				prevMaterial = elem.material;
-				index0 = iElem * elementSize;
-				numGroupVerts = elementSize;
+			},
+			getRemainingString: function () {
 
-			} else {
+				return this.line.substring( this.currentCharIndex, this.lineLength );
 
-				numGroupVerts += elementSize;
+			},
+			isAtTheEnd: function () {
 
-			}
+				return this.currentCharIndex >= this.lineLength;
 
-		}
+			},
+			setToEnd: function () {
 
-		if ( numGroupVerts > 0 ) {
+				this.currentCharIndex = this.lineLength;
 
-			bufferGeometry.addGroup( index0, Infinity, materials.length - 1 );
+			},
+			getLineNumberString: function () {
 
-		}
+				return this.lineNumber >= 0 ? ' at line ' + this.lineNumber : '';
 
-		bufferGeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
+			}
+		};
 
-		if ( elementSize === 3 ) {
+		function sortByMaterial( a, b ) {
 
-			bufferGeometry.setAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) );
+			if ( a.colourCode === b.colourCode ) {
 
-		}
+				return 0;
 
-		var object3d = null;
+			}
 
-		if ( elementSize === 2 ) {
+			if ( a.colourCode < b.colourCode ) {
 
-			object3d = new THREE.LineSegments( bufferGeometry, materials );
+				return - 1;
 
-		} else if ( elementSize === 3 ) {
+			}
 
-			object3d = new THREE.Mesh( bufferGeometry, materials );
+			return 1;
 
 		}
 
-		if ( isConditionalSegments ) {
-
-			object3d.isConditionalLine = true;
-
-			var controlArray0 = new Float32Array( elements.length * 3 * 2 );
-			var controlArray1 = new Float32Array( elements.length * 3 * 2 );
-			var directionArray = new Float32Array( elements.length * 3 * 2 );
-			for ( var i = 0, l = elements.length; i < l; i ++ ) {
-
-				var os = elements[ i ];
-				var c0 = os.c0;
-				var c1 = os.c1;
-				var v0 = os.v0;
-				var v1 = os.v1;
-				var index = i * 3 * 2;
-				controlArray0[ index + 0 ] = c0.x;
-				controlArray0[ index + 1 ] = c0.y;
-				controlArray0[ index + 2 ] = c0.z;
-				controlArray0[ index + 3 ] = c0.x;
-				controlArray0[ index + 4 ] = c0.y;
-				controlArray0[ index + 5 ] = c0.z;
-
-				controlArray1[ index + 0 ] = c1.x;
-				controlArray1[ index + 1 ] = c1.y;
-				controlArray1[ index + 2 ] = c1.z;
-				controlArray1[ index + 3 ] = c1.x;
-				controlArray1[ index + 4 ] = c1.y;
-				controlArray1[ index + 5 ] = c1.z;
-
-				directionArray[ index + 0 ] = v1.x - v0.x;
-				directionArray[ index + 1 ] = v1.y - v0.y;
-				directionArray[ index + 2 ] = v1.z - v0.z;
-				directionArray[ index + 3 ] = v1.x - v0.x;
-				directionArray[ index + 4 ] = v1.y - v0.y;
-				directionArray[ index + 5 ] = v1.z - v0.z;
-
-			}
+		function createObject( elements, elementSize, isConditionalSegments ) {
 
-			bufferGeometry.setAttribute( 'control0', new THREE.BufferAttribute( controlArray0, 3, false ) );
-			bufferGeometry.setAttribute( 'control1', new THREE.BufferAttribute( controlArray1, 3, false ) );
-			bufferGeometry.setAttribute( 'direction', new THREE.BufferAttribute( directionArray, 3, false ) );
+			// Creates a THREE.LineSegments (elementSize = 2) or a THREE.Mesh (elementSize = 3 )
+			// With per face / segment material, implemented with mesh groups and materials array
+			// Sort the triangles or line segments by colour code to make later the mesh groups
+			elements.sort( sortByMaterial );
+			var positions = [];
+			var normals = [];
+			var materials = [];
+			var bufferGeometry = new THREE.BufferGeometry();
+			var prevMaterial = null;
+			var index0 = 0;
+			var numGroupVerts = 0;
 
-		}
+			for ( var iElem = 0, nElem = elements.length; iElem < nElem; iElem ++ ) {
 
-		return object3d;
+				var elem = elements[ iElem ];
+				var v0 = elem.v0;
+				var v1 = elem.v1; // Note that LDraw coordinate system is rotated 180 deg. in the X axis w.r.t. Three.js's one
 
-	}
+				positions.push( v0.x, v0.y, v0.z, v1.x, v1.y, v1.z );
 
-	//
+				if ( elementSize === 3 ) {
 
-	function LDrawLoader( manager ) {
+					positions.push( elem.v2.x, elem.v2.y, elem.v2.z );
+					var n0 = elem.n0 || elem.faceNormal;
+					var n1 = elem.n1 || elem.faceNormal;
+					var n2 = elem.n2 || elem.faceNormal;
+					normals.push( n0.x, n0.y, n0.z );
+					normals.push( n1.x, n1.y, n1.z );
+					normals.push( n2.x, n2.y, n2.z );
 
-		THREE.Loader.call( this, manager );
+				}
 
-		// This is a stack of 'parse scopes' with one level per subobject loaded file.
-		// Each level contains a material lib and also other runtime variables passed between parent and child subobjects
-		// When searching for a material code, the stack is read from top of the stack to bottom
-		// Each material library is an object map keyed by colour codes.
-		this.parseScopesStack = null;
+				if ( prevMaterial !== elem.material ) {
 
-		// Array of THREE.Material
-		this.materials = [];
+					if ( prevMaterial !== null ) {
 
-		// Not using THREE.Cache here because it returns the previous HTML error response instead of calling onError()
-		// This also allows to handle the embedded text files ("0 FILE" lines)
-		this.subobjectCache = {};
+						bufferGeometry.addGroup( index0, numGroupVerts, materials.length - 1 );
 
-		// This object is a map from file names to paths. It agilizes the paths search. If it is not set then files will be searched by trial and error.
-		this.fileMap = null;
+					}
 
-		// Add default main triangle and line edge materials (used in piecess that can be coloured with a main color)
-		this.setMaterials( [
-			this.parseColourMetaDirective( new LineParser( 'Main_Colour CODE 16 VALUE #FF8080 EDGE #333333' ) ),
-			this.parseColourMetaDirective( new LineParser( 'Edge_Colour CODE 24 VALUE #A0A0A0 EDGE #333333' ) )
-		] );
+					materials.push( elem.material );
+					prevMaterial = elem.material;
+					index0 = iElem * elementSize;
+					numGroupVerts = elementSize;
 
-		// If this flag is set to true, each subobject will be a THREE.Object.
-		// If not (the default), only one object which contains all the merged primitives will be created.
-		this.separateObjects = false;
+				} else {
 
-		// If this flag is set to true the vertex normals will be smoothed.
-		this.smoothNormals = true;
+					numGroupVerts += elementSize;
 
-	}
+				}
 
-	// Special surface finish tag types.
-	// Note: "MATERIAL" tag (e.g. GLITTER, SPECKLE) is not implemented
-	LDrawLoader.FINISH_TYPE_DEFAULT = 0;
-	LDrawLoader.FINISH_TYPE_CHROME = 1;
-	LDrawLoader.FINISH_TYPE_PEARLESCENT = 2;
-	LDrawLoader.FINISH_TYPE_RUBBER = 3;
-	LDrawLoader.FINISH_TYPE_MATTE_METALLIC = 4;
-	LDrawLoader.FINISH_TYPE_METAL = 5;
+			}
 
-	// State machine to search a subobject path.
-	// The LDraw standard establishes these various possible subfolders.
-	LDrawLoader.FILE_LOCATION_AS_IS = 0;
-	LDrawLoader.FILE_LOCATION_TRY_PARTS = 1;
-	LDrawLoader.FILE_LOCATION_TRY_P = 2;
-	LDrawLoader.FILE_LOCATION_TRY_MODELS = 3;
-	LDrawLoader.FILE_LOCATION_TRY_RELATIVE = 4;
-	LDrawLoader.FILE_LOCATION_TRY_ABSOLUTE = 5;
-	LDrawLoader.FILE_LOCATION_NOT_FOUND = 6;
+			if ( numGroupVerts > 0 ) {
 
-	LDrawLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+				bufferGeometry.addGroup( index0, Infinity, materials.length - 1 );
 
-		constructor: LDrawLoader,
+			}
 
-		load: function ( url, onLoad, onProgress, onError ) {
+			bufferGeometry.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
 
-			if ( ! this.fileMap ) {
+			if ( elementSize === 3 ) {
 
-				this.fileMap = {};
+				bufferGeometry.setAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) );
 
 			}
 
-			var scope = this;
+			var object3d = null;
 
-			var fileLoader = new THREE.FileLoader( this.manager );
-			fileLoader.setPath( this.path );
-			fileLoader.setRequestHeader( this.requestHeader );
-			fileLoader.setWithCredentials( this.withCredentials );
-			fileLoader.load( url, function ( text ) {
+			if ( elementSize === 2 ) {
 
-				scope.processObject( text, onLoad, null, url );
+				object3d = new THREE.LineSegments( bufferGeometry, materials );
 
-			}, onProgress, onError );
+			} else if ( elementSize === 3 ) {
 
-		},
+				object3d = new THREE.Mesh( bufferGeometry, materials );
 
-		parse: function ( text, path, onLoad ) {
+			}
 
-			// Async parse.  This function calls onParse with the parsed THREE.Object3D as parameter
+			if ( isConditionalSegments ) {
+
+				object3d.isConditionalLine = true;
+				var controlArray0 = new Float32Array( elements.length * 3 * 2 );
+				var controlArray1 = new Float32Array( elements.length * 3 * 2 );
+				var directionArray = new Float32Array( elements.length * 3 * 2 );
+
+				for ( var i = 0, l = elements.length; i < l; i ++ ) {
+
+					var os = elements[ i ];
+					var c0 = os.c0;
+					var c1 = os.c1;
+					var v0 = os.v0;
+					var v1 = os.v1;
+					var index = i * 3 * 2;
+					controlArray0[ index + 0 ] = c0.x;
+					controlArray0[ index + 1 ] = c0.y;
+					controlArray0[ index + 2 ] = c0.z;
+					controlArray0[ index + 3 ] = c0.x;
+					controlArray0[ index + 4 ] = c0.y;
+					controlArray0[ index + 5 ] = c0.z;
+					controlArray1[ index + 0 ] = c1.x;
+					controlArray1[ index + 1 ] = c1.y;
+					controlArray1[ index + 2 ] = c1.z;
+					controlArray1[ index + 3 ] = c1.x;
+					controlArray1[ index + 4 ] = c1.y;
+					controlArray1[ index + 5 ] = c1.z;
+					directionArray[ index + 0 ] = v1.x - v0.x;
+					directionArray[ index + 1 ] = v1.y - v0.y;
+					directionArray[ index + 2 ] = v1.z - v0.z;
+					directionArray[ index + 3 ] = v1.x - v0.x;
+					directionArray[ index + 4 ] = v1.y - v0.y;
+					directionArray[ index + 5 ] = v1.z - v0.z;
 
-			this.processObject( text, onLoad, null, path );
+				}
 
-		},
+				bufferGeometry.setAttribute( 'control0', new THREE.BufferAttribute( controlArray0, 3, false ) );
+				bufferGeometry.setAttribute( 'control1', new THREE.BufferAttribute( controlArray1, 3, false ) );
+				bufferGeometry.setAttribute( 'direction', new THREE.BufferAttribute( directionArray, 3, false ) );
 
-		setMaterials: function ( materials ) {
+			}
 
-			// Clears parse scopes stack, adds new scope with material library
+			return object3d;
 
-			this.parseScopesStack = [];
+		} //
 
-			this.newParseScopeLevel( materials );
 
-			this.getCurrentParseScope().isFromParse = false;
+		function LDrawLoader( manager ) {
 
-			this.materials = materials;
+			THREE.Loader.call( this, manager ); // This is a stack of 'parse scopes' with one level per subobject loaded file.
+			// Each level contains a material lib and also other runtime variables passed between parent and child subobjects
+			// When searching for a material code, the stack is read from top of the stack to bottom
+			// Each material library is an object map keyed by colour codes.
 
-			return this;
+			this.parseScopesStack = null; // Array of THREE.Material
 
-		},
+			this.materials = []; // Not using THREE.Cache here because it returns the previous HTML error response instead of calling onError()
+			// This also allows to handle the embedded text files ("0 FILE" lines)
 
-		setFileMap: function ( fileMap ) {
+			this.subobjectCache = {}; // This object is a map from file names to paths. It agilizes the paths search. If it is not set then files will be searched by trial and error.
 
-			this.fileMap = fileMap;
+			this.fileMap = null; // Add default main triangle and line edge materials (used in piecess that can be coloured with a main color)
 
-			return this;
+			this.setMaterials( [ this.parseColourMetaDirective( new LineParser( 'Main_Colour CODE 16 VALUE #FF8080 EDGE #333333' ) ), this.parseColourMetaDirective( new LineParser( 'Edge_Colour CODE 24 VALUE #A0A0A0 EDGE #333333' ) ) ] ); // If this flag is set to true, each subobject will be a Object.
+			// If not (the default), only one object which contains all the merged primitives will be created.
 
-		},
+			this.separateObjects = false; // If this flag is set to true the vertex normals will be smoothed.
 
-		newParseScopeLevel: function ( materials ) {
+			this.smoothNormals = true;
 
-			// Adds a new scope level, assign materials to it and returns it
+		} // Special surface finish tag types.
+		// Note: "MATERIAL" tag (e.g. GLITTER, SPECKLE) is not implemented
 
-			var matLib = {};
 
-			if ( materials ) {
+		LDrawLoader.FINISH_TYPE_DEFAULT = 0;
+		LDrawLoader.FINISH_TYPE_CHROME = 1;
+		LDrawLoader.FINISH_TYPE_PEARLESCENT = 2;
+		LDrawLoader.FINISH_TYPE_RUBBER = 3;
+		LDrawLoader.FINISH_TYPE_MATTE_METALLIC = 4;
+		LDrawLoader.FINISH_TYPE_METAL = 5; // State machine to search a subobject path.
+		// The LDraw standard establishes these various possible subfolders.
 
-				for ( var i = 0, n = materials.length; i < n; i ++ ) {
+		LDrawLoader.FILE_LOCATION_AS_IS = 0;
+		LDrawLoader.FILE_LOCATION_TRY_PARTS = 1;
+		LDrawLoader.FILE_LOCATION_TRY_P = 2;
+		LDrawLoader.FILE_LOCATION_TRY_MODELS = 3;
+		LDrawLoader.FILE_LOCATION_TRY_RELATIVE = 4;
+		LDrawLoader.FILE_LOCATION_TRY_ABSOLUTE = 5;
+		LDrawLoader.FILE_LOCATION_NOT_FOUND = 6;
+		LDrawLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+			constructor: LDrawLoader,
+			load: function ( url, onLoad, onProgress, onError ) {
 
-					var material = materials[ i ];
-					matLib[ material.userData.code ] = material;
+				if ( ! this.fileMap ) {
 
-				}
+					this.fileMap = {};
 
-			}
+				}
 
-			var topParseScope = this.getCurrentParseScope();
-			var newParseScope = {
+				var scope = this;
+				var fileLoader = new THREE.FileLoader( this.manager );
+				fileLoader.setPath( this.path );
+				fileLoader.setRequestHeader( this.requestHeader );
+				fileLoader.setWithCredentials( this.withCredentials );
+				fileLoader.load( url, function ( text ) {
 
-				lib: matLib,
-				url: null,
+					scope.processObject( text, onLoad, null, url );
 
-				// Subobjects
-				subobjects: null,
-				numSubobjects: 0,
-				subobjectIndex: 0,
-				inverted: false,
-				category: null,
-				keywords: null,
+				}, onProgress, onError );
 
-				// Current subobject
-				currentFileName: null,
-				mainColourCode: topParseScope ? topParseScope.mainColourCode : '16',
-				mainEdgeColourCode: topParseScope ? topParseScope.mainEdgeColourCode : '24',
-				currentMatrix: new THREE.Matrix4(),
-				matrix: new THREE.Matrix4(),
+			},
+			parse: function ( text, path, onLoad ) {
 
-				// If false, it is a root material scope previous to parse
-				isFromParse: true,
+				// Async parse.	This function calls onParse with the parsed THREE.Object3D as parameter
+				this.processObject( text, onLoad, null, path );
 
-				triangles: null,
-				lineSegments: null,
-				conditionalSegments: null,
+			},
+			setMaterials: function ( materials ) {
 
-				// If true, this object is the start of a construction step
-				startingConstructionStep: false
-			};
+				// Clears parse scopes stack, adds new scope with material library
+				this.parseScopesStack = [];
+				this.newParseScopeLevel( materials );
+				this.getCurrentParseScope().isFromParse = false;
+				this.materials = materials;
+				return this;
 
-			this.parseScopesStack.push( newParseScope );
+			},
+			setFileMap: function ( fileMap ) {
 
-			return newParseScope;
+				this.fileMap = fileMap;
+				return this;
 
-		},
+			},
+			newParseScopeLevel: function ( materials ) {
 
-		removeScopeLevel: function () {
+				// Adds a new scope level, assign materials to it and returns it
+				var matLib = {};
 
-			this.parseScopesStack.pop();
+				if ( materials ) {
 
-			return this;
+					for ( var i = 0, n = materials.length; i < n; i ++ ) {
 
-		},
+						var material = materials[ i ];
+						matLib[ material.userData.code ] = material;
 
-		addMaterial: function ( material ) {
+					}
 
-			// Adds a material to the material library which is on top of the parse scopes stack. And also to the materials array
+				}
 
-			var matLib = this.getCurrentParseScope().lib;
+				var topParseScope = this.getCurrentParseScope();
+				var newParseScope = {
+					lib: matLib,
+					url: null,
+					// Subobjects
+					subobjects: null,
+					numSubobjects: 0,
+					subobjectIndex: 0,
+					inverted: false,
+					category: null,
+					keywords: null,
+					// Current subobject
+					currentFileName: null,
+					mainColourCode: topParseScope ? topParseScope.mainColourCode : '16',
+					mainEdgeColourCode: topParseScope ? topParseScope.mainEdgeColourCode : '24',
+					currentMatrix: new THREE.Matrix4(),
+					matrix: new THREE.Matrix4(),
+					// If false, it is a root material scope previous to parse
+					isFromParse: true,
+					triangles: null,
+					lineSegments: null,
+					conditionalSegments: null,
+					// If true, this object is the start of a construction step
+					startingConstructionStep: false
+				};
+				this.parseScopesStack.push( newParseScope );
+				return newParseScope;
+
+			},
+			removeScopeLevel: function () {
+
+				this.parseScopesStack.pop();
+				return this;
+
+			},
+			addMaterial: function ( material ) {
+
+				// Adds a material to the material library which is on top of the parse scopes stack. And also to the materials array
+				var matLib = this.getCurrentParseScope().lib;
+
+				if ( ! matLib[ material.userData.code ] ) {
+
+					this.materials.push( material );
 
-			if ( ! matLib[ material.userData.code ] ) {
+				}
 
-				this.materials.push( material );
+				matLib[ material.userData.code ] = material;
+				return this;
 
-			}
+			},
+			getMaterial: function ( colourCode ) {
 
-			matLib[ material.userData.code ] = material;
+				// Given a colour code search its material in the parse scopes stack
+				if ( colourCode.startsWith( '0x2' ) ) {
 
-			return this;
+					// Special 'direct' material value (RGB colour)
+					var colour = colourCode.substring( 3 );
+					return this.parseColourMetaDirective( new LineParser( 'Direct_Color_' + colour + ' CODE -1 VALUE #' + colour + ' EDGE #' + colour + '' ) );
 
-		},
+				}
 
-		getMaterial: function ( colourCode ) {
+				for ( var i = this.parseScopesStack.length - 1; i >= 0; i -- ) {
 
-			// Given a colour code search its material in the parse scopes stack
+					var material = this.parseScopesStack[ i ].lib[ colourCode ];
 
-			if ( colourCode.startsWith( '0x2' ) ) {
+					if ( material ) {
 
-				// Special 'direct' material value (RGB colour)
+						return material;
 
-				var colour = colourCode.substring( 3 );
+					}
 
-				return this.parseColourMetaDirective( new LineParser( 'Direct_Color_' + colour + ' CODE -1 VALUE #' + colour + ' EDGE #' + colour + '' ) );
+				} // Material was not found
 
-			}
 
-			for ( var i = this.parseScopesStack.length - 1; i >= 0; i -- ) {
+				return null;
 
-				var material = this.parseScopesStack[ i ].lib[ colourCode ];
+			},
+			getParentParseScope: function () {
 
-				if ( material ) {
+				if ( this.parseScopesStack.length > 1 ) {
 
-					return material;
+					return this.parseScopesStack[ this.parseScopesStack.length - 2 ];
 
 				}
 
-			}
+				return null;
 
-			// Material was not found
-			return null;
+			},
+			getCurrentParseScope: function () {
 
-		},
+				if ( this.parseScopesStack.length > 0 ) {
 
-		getParentParseScope: function () {
+					return this.parseScopesStack[ this.parseScopesStack.length - 1 ];
 
-			if ( this.parseScopesStack.length > 1 ) {
+				}
 
-				return this.parseScopesStack[ this.parseScopesStack.length - 2 ];
+				return null;
 
-			}
+			},
+			parseColourMetaDirective: function ( lineParser ) {
 
-			return null;
+				// Parses a colour definition and returns a THREE.Material or null if error
+				var code = null; // Triangle and line colours
 
-		},
+				var colour = 0xFF00FF;
+				var edgeColour = 0xFF00FF; // Transparency
 
-		getCurrentParseScope: function () {
+				var alpha = 1;
+				var isTransparent = false; // Self-illumination:
 
-			if ( this.parseScopesStack.length > 0 ) {
+				var luminance = 0;
+				var finishType = LDrawLoader.FINISH_TYPE_DEFAULT;
+				var canHaveEnvMap = true;
+				var edgeMaterial = null;
+				var name = lineParser.getToken();
 
-				return this.parseScopesStack[ this.parseScopesStack.length - 1 ];
+				if ( ! name ) {
 
-			}
+					throw 'LDrawLoader: Material name was expected after "!COLOUR tag' + lineParser.getLineNumberString() + '.';
 
-			return null;
+				} // Parse tag tokens and their parameters
 
-		},
 
-		parseColourMetaDirective: function ( lineParser ) {
+				var token = null;
 
-			// Parses a colour definition and returns a THREE.Material or null if error
+				while ( true ) {
 
-			var code = null;
+					token = lineParser.getToken();
 
-			// Triangle and line colours
-			var colour = 0xFF00FF;
-			var edgeColour = 0xFF00FF;
+					if ( ! token ) {
 
-			// Transparency
-			var alpha = 1;
-			var isTransparent = false;
-			// Self-illumination:
-			var luminance = 0;
-
-			var finishType = LDrawLoader.FINISH_TYPE_DEFAULT;
-			var canHaveEnvMap = true;
+						break;
 
-			var edgeMaterial = null;
+					}
 
-			var name = lineParser.getToken();
-			if ( ! name ) {
+					switch ( token.toUpperCase() ) {
 
-				throw 'LDrawLoader: Material name was expected after "!COLOUR tag' + lineParser.getLineNumberString() + '.';
+						case 'CODE':
+							code = lineParser.getToken();
+							break;
 
-			}
+						case 'VALUE':
+							colour = lineParser.getToken();
 
-			// Parse tag tokens and their parameters
-			var token = null;
-			while ( true ) {
+							if ( colour.startsWith( '0x' ) ) {
 
-				token = lineParser.getToken();
+								colour = '#' + colour.substring( 2 );
 
-				if ( ! token ) {
+							} else if ( ! colour.startsWith( '#' ) ) {
 
-					break;
+								throw 'LDrawLoader: Invalid colour while parsing material' + lineParser.getLineNumberString() + '.';
 
-				}
+							}
 
-				switch ( token.toUpperCase() ) {
+							break;
 
-					case 'CODE':
+						case 'EDGE':
+							edgeColour = lineParser.getToken();
 
-						code = lineParser.getToken();
-						break;
+							if ( edgeColour.startsWith( '0x' ) ) {
 
-					case 'VALUE':
+								edgeColour = '#' + edgeColour.substring( 2 );
 
-						colour = lineParser.getToken();
-						if ( colour.startsWith( '0x' ) ) {
+							} else if ( ! edgeColour.startsWith( '#' ) ) {
 
-							colour = '#' + colour.substring( 2 );
+								// Try to see if edge colour is a colour code
+								edgeMaterial = this.getMaterial( edgeColour );
 
-						} else if ( ! colour.startsWith( '#' ) ) {
+								if ( ! edgeMaterial ) {
 
-							throw 'LDrawLoader: Invalid colour while parsing material' + lineParser.getLineNumberString() + '.';
+									throw 'LDrawLoader: Invalid edge colour while parsing material' + lineParser.getLineNumberString() + '.';
 
-						}
+								} // Get the edge material for this triangle material
 
-						break;
 
-					case 'EDGE':
+								edgeMaterial = edgeMaterial.userData.edgeMaterial;
 
-						edgeColour = lineParser.getToken();
-						if ( edgeColour.startsWith( '0x' ) ) {
+							}
 
-							edgeColour = '#' + edgeColour.substring( 2 );
+							break;
 
-						} else if ( ! edgeColour.startsWith( '#' ) ) {
+						case 'ALPHA':
+							alpha = parseInt( lineParser.getToken() );
 
-							// Try to see if edge colour is a colour code
-							edgeMaterial = this.getMaterial( edgeColour );
-							if ( ! edgeMaterial ) {
+							if ( isNaN( alpha ) ) {
 
-								throw 'LDrawLoader: Invalid edge colour while parsing material' + lineParser.getLineNumberString() + '.';
+								throw 'LDrawLoader: Invalid alpha value in material definition' + lineParser.getLineNumberString() + '.';
 
 							}
 
-							// Get the edge material for this triangle material
-							edgeMaterial = edgeMaterial.userData.edgeMaterial;
+							alpha = Math.max( 0, Math.min( 1, alpha / 255 ) );
 
-						}
+							if ( alpha < 1 ) {
 
-						break;
+								isTransparent = true;
 
-					case 'ALPHA':
+							}
 
-						alpha = parseInt( lineParser.getToken() );
+							break;
 
-						if ( isNaN( alpha ) ) {
+						case 'LUMINANCE':
+							luminance = parseInt( lineParser.getToken() );
 
-							throw 'LDrawLoader: Invalid alpha value in material definition' + lineParser.getLineNumberString() + '.';
+							if ( isNaN( luminance ) ) {
 
-						}
+								throw 'LDrawLoader: Invalid luminance value in material definition' + LineParser.getLineNumberString() + '.';
+
+							}
 
-						alpha = Math.max( 0, Math.min( 1, alpha / 255 ) );
+							luminance = Math.max( 0, Math.min( 1, luminance / 255 ) );
+							break;
 
-						if ( alpha < 1 ) {
+						case 'CHROME':
+							finishType = LDrawLoader.FINISH_TYPE_CHROME;
+							break;
 
-							isTransparent = true;
+						case 'PEARLESCENT':
+							finishType = LDrawLoader.FINISH_TYPE_PEARLESCENT;
+							break;
 
-						}
+						case 'RUBBER':
+							finishType = LDrawLoader.FINISH_TYPE_RUBBER;
+							break;
 
-						break;
+						case 'MATTE_METALLIC':
+							finishType = LDrawLoader.FINISH_TYPE_MATTE_METALLIC;
+							break;
 
-					case 'LUMINANCE':
+						case 'METAL':
+							finishType = LDrawLoader.FINISH_TYPE_METAL;
+							break;
 
-						luminance = parseInt( lineParser.getToken() );
+						case 'MATERIAL':
+						// Not implemented
+							lineParser.setToEnd();
+							break;
 
-						if ( isNaN( luminance ) ) {
+						default:
+							throw 'LDrawLoader: Unknown token "' + token + '" while parsing material' + lineParser.getLineNumberString() + '.';
+							break;
 
-							throw 'LDrawLoader: Invalid luminance value in material definition' + LineParser.getLineNumberString() + '.';
+					}
 
-						}
+				}
 
-						luminance = Math.max( 0, Math.min( 1, luminance / 255 ) );
+				var material = null;
 
-						break;
+				switch ( finishType ) {
 
-					case 'CHROME':
-						finishType = LDrawLoader.FINISH_TYPE_CHROME;
+					case LDrawLoader.FINISH_TYPE_DEFAULT:
+						material = new THREE.MeshStandardMaterial( {
+							color: colour,
+							roughness: 0.3,
+							envMapIntensity: 0.3,
+							metalness: 0
+						} );
 						break;
 
-					case 'PEARLESCENT':
-						finishType = LDrawLoader.FINISH_TYPE_PEARLESCENT;
+					case LDrawLoader.FINISH_TYPE_PEARLESCENT:
+					// Try to imitate pearlescency by setting the specular to the complementary of the color, and low shininess
+						var specular = new THREE.Color( colour );
+						var hsl = specular.getHSL( {
+							h: 0,
+							s: 0,
+							l: 0
+						} );
+						hsl.h = ( hsl.h + 0.5 ) % 1;
+						hsl.l = Math.min( 1, hsl.l + ( 1 - hsl.l ) * 0.7 );
+						specular.setHSL( hsl.h, hsl.s, hsl.l );
+						material = new THREE.MeshPhongMaterial( {
+							color: colour,
+							specular: specular,
+							shininess: 10,
+							reflectivity: 0.3
+						} );
 						break;
 
-					case 'RUBBER':
-						finishType = LDrawLoader.FINISH_TYPE_RUBBER;
+					case LDrawLoader.FINISH_TYPE_CHROME:
+					// Mirror finish surface
+						material = new THREE.MeshStandardMaterial( {
+							color: colour,
+							roughness: 0,
+							metalness: 1
+						} );
 						break;
 
-					case 'MATTE_METALLIC':
-						finishType = LDrawLoader.FINISH_TYPE_MATTE_METALLIC;
+					case LDrawLoader.FINISH_TYPE_RUBBER:
+					// Rubber finish
+						material = new THREE.MeshStandardMaterial( {
+							color: colour,
+							roughness: 0.9,
+							metalness: 0
+						} );
+						canHaveEnvMap = false;
 						break;
 
-					case 'METAL':
-						finishType = LDrawLoader.FINISH_TYPE_METAL;
+					case LDrawLoader.FINISH_TYPE_MATTE_METALLIC:
+					// Brushed metal finish
+						material = new THREE.MeshStandardMaterial( {
+							color: colour,
+							roughness: 0.8,
+							metalness: 0.4
+						} );
 						break;
 
-					case 'MATERIAL':
-						// Not implemented
-						lineParser.setToEnd();
+					case LDrawLoader.FINISH_TYPE_METAL:
+					// Average metal finish
+						material = new THREE.MeshStandardMaterial( {
+							color: colour,
+							roughness: 0.2,
+							metalness: 0.85
+						} );
 						break;
 
 					default:
-						throw 'LDrawLoader: Unknown token "' + token + '" while parsing material' + lineParser.getLineNumberString() + '.';
+					// Should not happen
 						break;
 
 				}
 
-			}
-
-			var material = null;
-
-			switch ( finishType ) {
-
-				case LDrawLoader.FINISH_TYPE_DEFAULT:
-
-					material = new THREE.MeshStandardMaterial( { color: colour, roughness: 0.3, envMapIntensity: 0.3, metalness: 0 } );
-					break;
-
-				case LDrawLoader.FINISH_TYPE_PEARLESCENT:
-
-					// Try to imitate pearlescency by setting the specular to the complementary of the color, and low shininess
-					var specular = new THREE.Color( colour );
-					var hsl = specular.getHSL( { h: 0, s: 0, l: 0 } );
-					hsl.h = ( hsl.h + 0.5 ) % 1;
-					hsl.l = Math.min( 1, hsl.l + ( 1 - hsl.l ) * 0.7 );
-					specular.setHSL( hsl.h, hsl.s, hsl.l );
-
-					material = new THREE.MeshPhongMaterial( { color: colour, specular: specular, shininess: 10, reflectivity: 0.3 } );
-					break;
+				material.transparent = isTransparent;
+				material.premultipliedAlpha = true;
+				material.opacity = alpha;
+				material.depthWrite = ! isTransparent;
+				material.polygonOffset = true;
+				material.polygonOffsetFactor = 1;
+				material.userData.canHaveEnvMap = canHaveEnvMap;
 
-				case LDrawLoader.FINISH_TYPE_CHROME:
-
-					// Mirror finish surface
-					material = new THREE.MeshStandardMaterial( { color: colour, roughness: 0, metalness: 1 } );
-					break;
-
-				case LDrawLoader.FINISH_TYPE_RUBBER:
-
-					// Rubber finish
-					material = new THREE.MeshStandardMaterial( { color: colour, roughness: 0.9, metalness: 0 } );
-					canHaveEnvMap = false;
-					break;
+				if ( luminance !== 0 ) {
 
-				case LDrawLoader.FINISH_TYPE_MATTE_METALLIC:
+					material.emissive.set( material.color ).multiplyScalar( luminance );
 
-					// Brushed metal finish
-					material = new THREE.MeshStandardMaterial( { color: colour, roughness: 0.8, metalness: 0.4 } );
-					break;
-
-				case LDrawLoader.FINISH_TYPE_METAL:
-
-					// Average metal finish
-					material = new THREE.MeshStandardMaterial( { color: colour, roughness: 0.2, metalness: 0.85 } );
-					break;
-
-				default:
-					// Should not happen
-					break;
-
-			}
-
-			material.transparent = isTransparent;
-			material.premultipliedAlpha = true;
-			material.opacity = alpha;
-			material.depthWrite = ! isTransparent;
-
-			material.polygonOffset = true;
-			material.polygonOffsetFactor = 1;
-
-			material.userData.canHaveEnvMap = canHaveEnvMap;
-
-			if ( luminance !== 0 ) {
-
-				material.emissive.set( material.color ).multiplyScalar( luminance );
-
-			}
+				}
 
-			if ( ! edgeMaterial ) {
+				if ( ! edgeMaterial ) {
 
-				// This is the material used for edges
-				edgeMaterial = new THREE.LineBasicMaterial( {
-					color: edgeColour,
-					transparent: isTransparent,
-					opacity: alpha,
-					depthWrite: ! isTransparent
-				} );
-				edgeMaterial.userData.code = code;
-				edgeMaterial.name = name + ' - Edge';
-				edgeMaterial.userData.canHaveEnvMap = false;
-
-				// This is the material used for conditional edges
-				edgeMaterial.userData.conditionalEdgeMaterial = new THREE.ShaderMaterial( {
-					vertexShader: conditionalLineVertShader,
-					fragmentShader: conditionalLineFragShader,
-					uniforms: THREE.UniformsUtils.merge( [
-						THREE.UniformsLib.fog,
-						{
+					// This is the material used for edges
+					edgeMaterial = new THREE.LineBasicMaterial( {
+						color: edgeColour,
+						transparent: isTransparent,
+						opacity: alpha,
+						depthWrite: ! isTransparent
+					} );
+					edgeMaterial.userData.code = code;
+					edgeMaterial.name = name + ' - Edge';
+					edgeMaterial.userData.canHaveEnvMap = false; // This is the material used for conditional edges
+
+					edgeMaterial.userData.conditionalEdgeMaterial = new THREE.ShaderMaterial( {
+						vertexShader: conditionalLineVertShader,
+						fragmentShader: conditionalLineFragShader,
+						uniforms: THREE.UniformsUtils.merge( [ THREE.UniformsLib.fog, {
 							diffuse: {
 								value: new THREE.Color( edgeColour )
 							},
 							opacity: {
 								value: alpha
 							}
-						}
-					] ),
-					fog: true,
-					transparent: isTransparent,
-					depthWrite: ! isTransparent
-				} );
-				edgeMaterial.userData.conditionalEdgeMaterial.userData.canHaveEnvMap = false;
-
-			}
-
-			material.userData.code = code;
-			material.name = name;
-
-			material.userData.edgeMaterial = edgeMaterial;
-
-			return material;
-
-		},
-
-		//
-
-		objectParse: function ( text ) {
-
-			// Retrieve data from the parent parse scope
-			var parentParseScope = this.getParentParseScope();
-
-			// Main colour codes passed to this subobject (or default codes 16 and 24 if it is the root object)
-			var mainColourCode = parentParseScope.mainColourCode;
-			var mainEdgeColourCode = parentParseScope.mainEdgeColourCode;
+						} ] ),
+						fog: true,
+						transparent: isTransparent,
+						depthWrite: ! isTransparent
+					} );
+					edgeMaterial.userData.conditionalEdgeMaterial.userData.canHaveEnvMap = false;
 
-			var currentParseScope = this.getCurrentParseScope();
+				}
 
-			// Parse result variables
-			var triangles;
-			var lineSegments;
-			var conditionalSegments;
+				material.userData.code = code;
+				material.name = name;
+				material.userData.edgeMaterial = edgeMaterial;
+				return material;
 
-			var subobjects = [];
+			},
+			//
+			objectParse: function ( text ) {
 
-			var category = null;
-			var keywords = null;
+				// Retrieve data from the parent parse scope
+				var parentParseScope = this.getParentParseScope(); // Main colour codes passed to this subobject (or default codes 16 and 24 if it is the root object)
 
-			if ( text.indexOf( '\r\n' ) !== - 1 ) {
+				var mainColourCode = parentParseScope.mainColourCode;
+				var mainEdgeColourCode = parentParseScope.mainEdgeColourCode;
+				var currentParseScope = this.getCurrentParseScope(); // Parse result variables
 
-				// This is faster than String.split with regex that splits on both
-				text = text.replace( /\r\n/g, '\n' );
+				var triangles;
+				var lineSegments;
+				var conditionalSegments;
+				var subobjects = [];
+				var category = null;
+				var keywords = null;
 
-			}
+				if ( text.indexOf( '\r\n' ) !== - 1 ) {
 
-			var lines = text.split( '\n' );
-			var numLines = lines.length;
-			var lineIndex = 0;
+					// This is faster than String.split with regex that splits on both
+					text = text.replace( /\r\n/g, '\n' );
 
-			var parsingEmbeddedFiles = false;
-			var currentEmbeddedFileName = null;
-			var currentEmbeddedText = null;
+				}
 
-			var bfcCertified = false;
-			var bfcCCW = true;
-			var bfcInverted = false;
-			var bfcCull = true;
-			var type = '';
+				var lines = text.split( '\n' );
+				var numLines = lines.length;
+				var lineIndex = 0;
+				var parsingEmbeddedFiles = false;
+				var currentEmbeddedFileName = null;
+				var currentEmbeddedText = null;
+				var bfcCertified = false;
+				var bfcCCW = true;
+				var bfcInverted = false;
+				var bfcCull = true;
+				var type = '';
+				var startingConstructionStep = false;
+				var scope = this;
 
-			var startingConstructionStep = false;
+				function parseColourCode( lineParser, forEdge ) {
 
-			var scope = this;
-			function parseColourCode( lineParser, forEdge ) {
+					// Parses next colour code and returns a THREE.Material
+					var colourCode = lineParser.getToken();
 
-				// Parses next colour code and returns a THREE.Material
+					if ( ! forEdge && colourCode === '16' ) {
 
-				var colourCode = lineParser.getToken();
+						colourCode = mainColourCode;
 
-				if ( ! forEdge && colourCode === '16' ) {
+					}
 
-					colourCode = mainColourCode;
+					if ( forEdge && colourCode === '24' ) {
 
-				}
+						colourCode = mainEdgeColourCode;
 
-				if ( forEdge && colourCode === '24' ) {
+					}
 
-					colourCode = mainEdgeColourCode;
+					var material = scope.getMaterial( colourCode );
 
-				}
+					if ( ! material ) {
 
-				var material = scope.getMaterial( colourCode );
+						throw 'LDrawLoader: Unknown colour code "' + colourCode + '" is used' + lineParser.getLineNumberString() + ' but it was not defined previously.';
 
-				if ( ! material ) {
+					}
 
-					throw 'LDrawLoader: Unknown colour code "' + colourCode + '" is used' + lineParser.getLineNumberString() + ' but it was not defined previously.';
+					return material;
 
 				}
 
-				return material;
+				function parseVector( lp ) {
 
-			}
+					var v = new THREE.Vector3( parseFloat( lp.getToken() ), parseFloat( lp.getToken() ), parseFloat( lp.getToken() ) );
 
-			function parseVector( lp ) {
+					if ( ! scope.separateObjects ) {
 
-				var v = new THREE.Vector3( parseFloat( lp.getToken() ), parseFloat( lp.getToken() ), parseFloat( lp.getToken() ) );
+						v.applyMatrix4( currentParseScope.currentMatrix );
 
-				if ( ! scope.separateObjects ) {
+					}
 
-					v.applyMatrix4( currentParseScope.currentMatrix );
+					return v;
 
-				}
+				} // Parse all line commands
 
-				return v;
 
-			}
+				for ( lineIndex = 0; lineIndex < numLines; lineIndex ++ ) {
 
-			// Parse all line commands
-			for ( lineIndex = 0; lineIndex < numLines; lineIndex ++ ) {
+					var line = lines[ lineIndex ];
+					if ( line.length === 0 ) continue;
 
-				var line = lines[ lineIndex ];
+					if ( parsingEmbeddedFiles ) {
 
-				if ( line.length === 0 ) continue;
+						if ( line.startsWith( '0 FILE ' ) ) {
 
-				if ( parsingEmbeddedFiles ) {
+							// Save previous embedded file in the cache
+							this.subobjectCache[ currentEmbeddedFileName.toLowerCase() ] = currentEmbeddedText; // New embedded text file
 
-					if ( line.startsWith( '0 FILE ' ) ) {
+							currentEmbeddedFileName = line.substring( 7 );
+							currentEmbeddedText = '';
 
-						// Save previous embedded file in the cache
-						this.subobjectCache[ currentEmbeddedFileName.toLowerCase() ] = currentEmbeddedText;
+						} else {
 
-						// New embedded text file
-						currentEmbeddedFileName = line.substring( 7 );
-						currentEmbeddedText = '';
+							currentEmbeddedText += line + '\n';
 
-					} else {
+						}
 
-						currentEmbeddedText += line + '\n';
+						continue;
 
 					}
 
-					continue;
+					var lp = new LineParser( line, lineIndex + 1 );
+					lp.seekNonSpace();
 
-				}
+					if ( lp.isAtTheEnd() ) {
 
-				var lp = new LineParser( line, lineIndex + 1 );
+						// Empty line
+						continue;
 
-				lp.seekNonSpace();
+					} // Parse the line type
 
-				if ( lp.isAtTheEnd() ) {
 
-					// Empty line
-					continue;
+					var lineType = lp.getToken();
 
-				}
-
-				// Parse the line type
-				var lineType = lp.getToken();
-
-				switch ( lineType ) {
-
-					// Line type 0: Comment or META
-					case '0':
+					switch ( lineType ) {
 
+						// Line type 0: Comment or META
+						case '0':
 						// Parse meta directive
-						var meta = lp.getToken();
-
-						if ( meta ) {
-
-							switch ( meta ) {
+							var meta = lp.getToken();
 
-								case '!LDRAW_ORG':
+							if ( meta ) {
 
-									type = lp.getToken();
+								switch ( meta ) {
 
-									currentParseScope.triangles = [];
-									currentParseScope.lineSegments = [];
-									currentParseScope.conditionalSegments = [];
-									currentParseScope.type = type;
+									case '!LDRAW_ORG':
+										type = lp.getToken();
+										currentParseScope.triangles = [];
+										currentParseScope.lineSegments = [];
+										currentParseScope.conditionalSegments = [];
+										currentParseScope.type = type;
+										var isRoot = ! parentParseScope.isFromParse;
 
-									var isRoot = ! parentParseScope.isFromParse;
-									if ( isRoot || scope.separateObjects && ! isPrimitiveType( type ) ) {
+										if ( isRoot || scope.separateObjects && ! isPrimitiveType( type ) ) {
 
-										currentParseScope.groupObject = new THREE.Group();
+											currentParseScope.groupObject = new THREE.Group();
+											currentParseScope.groupObject.userData.startingConstructionStep = currentParseScope.startingConstructionStep;
 
-										currentParseScope.groupObject.userData.startingConstructionStep = currentParseScope.startingConstructionStep;
+										} // If the scale of the object is negated then the triangle winding order
+										// needs to be flipped.
 
-									}
-
-									// If the scale of the object is negated then the triangle winding order
-									// needs to be flipped.
-									var matrix = currentParseScope.matrix;
-									if (
-										matrix.determinant() < 0 && (
-											scope.separateObjects && isPrimitiveType( type ) ||
-											! scope.separateObjects
-										) ) {
 
-										currentParseScope.inverted = ! currentParseScope.inverted;
-
-									}
-
-									triangles = currentParseScope.triangles;
-									lineSegments = currentParseScope.lineSegments;
-									conditionalSegments = currentParseScope.conditionalSegments;
-
-									break;
+										var matrix = currentParseScope.matrix;
 
-								case '!COLOUR':
+										if ( matrix.determinant() < 0 && ( scope.separateObjects && isPrimitiveType( type ) || ! scope.separateObjects ) ) {
 
-									var material = this.parseColourMetaDirective( lp );
-									if ( material ) {
+											currentParseScope.inverted = ! currentParseScope.inverted;
 
-										this.addMaterial( material );
-
-									}	else {
-
-										console.warn( 'LDrawLoader: Error parsing material' + lp.getLineNumberString() );
-
-									}
-
-									break;
+										}
 
-								case '!CATEGORY':
+										triangles = currentParseScope.triangles;
+										lineSegments = currentParseScope.lineSegments;
+										conditionalSegments = currentParseScope.conditionalSegments;
+										break;
 
-									category = lp.getToken();
-									break;
+									case '!COLOUR':
+										var material = this.parseColourMetaDirective( lp );
 
-								case '!KEYWORDS':
+										if ( material ) {
 
-									var newKeywords = lp.getRemainingString().split( ',' );
-									if ( newKeywords.length > 0 ) {
+											this.addMaterial( material );
 
-										if ( ! keywords ) {
+										} else {
 
-											keywords = [];
+											console.warn( 'LDrawLoader: Error parsing material' + lp.getLineNumberString() );
 
 										}
 
-										newKeywords.forEach( function ( keyword ) {
+										break;
 
-											keywords.push( keyword.trim() );
+									case '!CATEGORY':
+										category = lp.getToken();
+										break;
 
-										} );
+									case '!KEYWORDS':
+										var newKeywords = lp.getRemainingString().split( ',' );
 
-									}
+										if ( newKeywords.length > 0 ) {
 
-									break;
+											if ( ! keywords ) {
 
-								case 'FILE':
+												keywords = [];
 
-									if ( lineIndex > 0 ) {
+											}
 
-										// Start embedded text files parsing
-										parsingEmbeddedFiles = true;
-										currentEmbeddedFileName = lp.getRemainingString();
-										currentEmbeddedText = '';
+											newKeywords.forEach( function ( keyword ) {
 
-										bfcCertified = false;
-										bfcCCW = true;
+												keywords.push( keyword.trim() );
 
-									}
-
-									break;
+											} );
 
-								case 'BFC':
-
-									// Changes to the backface culling state
-									while ( ! lp.isAtTheEnd() ) {
-
-										var token = lp.getToken();
-
-										switch ( token ) {
-
-											case 'CERTIFY':
-											case 'NOCERTIFY':
+										}
 
-												bfcCertified = token === 'CERTIFY';
-												bfcCCW = true;
+										break;
 
-												break;
+									case 'FILE':
+										if ( lineIndex > 0 ) {
 
-											case 'CW':
-											case 'CCW':
+											// Start embedded text files parsing
+											parsingEmbeddedFiles = true;
+											currentEmbeddedFileName = lp.getRemainingString();
+											currentEmbeddedText = '';
+											bfcCertified = false;
+											bfcCCW = true;
 
-												bfcCCW = token === 'CCW';
+										}
 
-												break;
+										break;
 
-											case 'INVERTNEXT':
+									case 'BFC':
+									// Changes to the backface culling state
+										while ( ! lp.isAtTheEnd() ) {
 
-												bfcInverted = true;
+											var token = lp.getToken();
 
-												break;
+											switch ( token ) {
 
-											case 'CLIP':
-											case 'NOCLIP':
+												case 'CERTIFY':
+												case 'NOCERTIFY':
+													bfcCertified = token === 'CERTIFY';
+													bfcCCW = true;
+													break;
 
-											  bfcCull = token === 'CLIP';
+												case 'CW':
+												case 'CCW':
+													bfcCCW = token === 'CCW';
+													break;
 
-												break;
+												case 'INVERTNEXT':
+													bfcInverted = true;
+													break;
 
-											default:
+												case 'CLIP':
+												case 'NOCLIP':
+													bfcCull = token === 'CLIP';
+													break;
 
-												console.warn( 'THREE.LDrawLoader: BFC directive "' + token + '" is unknown.' );
+												default:
+													console.warn( 'THREE.LDrawLoader: BFC directive "' + token + '" is unknown.' );
+													break;
 
-												break;
+											}
 
 										}
 
-									}
+										break;
 
-									break;
+									case 'STEP':
+										startingConstructionStep = true;
+										break;
 
-								case 'STEP':
-
-									startingConstructionStep = true;
-
-									break;
-
-								default:
+									default:
 									// Other meta directives are not implemented
-									break;
-
-							}
-
-						}
-
-						break;
-
-					// Line type 1: Sub-object file
-					case '1':
-
-						var material = parseColourCode( lp );
-
-						var posX = parseFloat( lp.getToken() );
-						var posY = parseFloat( lp.getToken() );
-						var posZ = parseFloat( lp.getToken() );
-						var m0 = parseFloat( lp.getToken() );
-						var m1 = parseFloat( lp.getToken() );
-						var m2 = parseFloat( lp.getToken() );
-						var m3 = parseFloat( lp.getToken() );
-						var m4 = parseFloat( lp.getToken() );
-						var m5 = parseFloat( lp.getToken() );
-						var m6 = parseFloat( lp.getToken() );
-						var m7 = parseFloat( lp.getToken() );
-						var m8 = parseFloat( lp.getToken() );
-
-						var matrix = new THREE.Matrix4().set(
-							m0, m1, m2, posX,
-							m3, m4, m5, posY,
-							m6, m7, m8, posZ,
-							0, 0, 0, 1
-						);
-
-						var fileName = lp.getRemainingString().trim().replace( /\\/g, '/' );
-
-						if ( scope.fileMap[ fileName ] ) {
-
-							// Found the subobject path in the preloaded file path map
-							fileName = scope.fileMap[ fileName ];
-
-						}	else {
-
-							// Standardized subfolders
-							if ( fileName.startsWith( 's/' ) ) {
-
-								fileName = 'parts/' + fileName;
-
-							} else if ( fileName.startsWith( '48/' ) ) {
+										break;
 
-								fileName = 'p/' + fileName;
+								}
 
 							}
 
-						}
+							break;
+							// Line type 1: Sub-object file
 
-						subobjects.push( {
-							material: material,
-							matrix: matrix,
-							fileName: fileName,
-							originalFileName: fileName,
-							locationState: LDrawLoader.FILE_LOCATION_AS_IS,
-							url: null,
-							triedLowerCase: false,
-							inverted: bfcInverted !== currentParseScope.inverted,
-							startingConstructionStep: startingConstructionStep
-						} );
+						case '1':
+							var material = parseColourCode( lp );
+							var posX = parseFloat( lp.getToken() );
+							var posY = parseFloat( lp.getToken() );
+							var posZ = parseFloat( lp.getToken() );
+							var m0 = parseFloat( lp.getToken() );
+							var m1 = parseFloat( lp.getToken() );
+							var m2 = parseFloat( lp.getToken() );
+							var m3 = parseFloat( lp.getToken() );
+							var m4 = parseFloat( lp.getToken() );
+							var m5 = parseFloat( lp.getToken() );
+							var m6 = parseFloat( lp.getToken() );
+							var m7 = parseFloat( lp.getToken() );
+							var m8 = parseFloat( lp.getToken() );
+							var matrix = new THREE.Matrix4().set( m0, m1, m2, posX, m3, m4, m5, posY, m6, m7, m8, posZ, 0, 0, 0, 1 );
+							var fileName = lp.getRemainingString().trim().replace( /\\/g, '/' );
 
-						bfcInverted = false;
+							if ( scope.fileMap[ fileName ] ) {
 
-						break;
+								// Found the subobject path in the preloaded file path map
+								fileName = scope.fileMap[ fileName ];
 
-					// Line type 2: Line segment
-					case '2':
+							} else {
 
-						var material = parseColourCode( lp, true );
+								// Standardized subfolders
+								if ( fileName.startsWith( 's/' ) ) {
 
-						var segment = {
-							material: material.userData.edgeMaterial,
-							colourCode: material.userData.code,
-							v0: parseVector( lp ),
-							v1: parseVector( lp )
-						};
-
-						lineSegments.push( segment );
-
-						break;
-
-					// Line type 5: Conditional Line segment
-					case '5':
-
-						var material = parseColourCode( lp, true );
-
-						var segment = {
-							material: material.userData.edgeMaterial.userData.conditionalEdgeMaterial,
-							colourCode: material.userData.code,
-							v0: parseVector( lp ),
-							v1: parseVector( lp ),
-							c0: parseVector( lp ),
-							c1: parseVector( lp )
-						};
-
-						conditionalSegments.push( segment );
-
-						break;
+									fileName = 'parts/' + fileName;
 
-					// Line type 3: Triangle
-					case '3':
+								} else if ( fileName.startsWith( '48/' ) ) {
 
-						var material = parseColourCode( lp );
+									fileName = 'p/' + fileName;
 
-						var inverted = currentParseScope.inverted;
-						var ccw = bfcCCW !== inverted;
-						var doubleSided = ! bfcCertified || ! bfcCull;
-						var v0, v1, v2, faceNormal;
-
-						if ( ccw === true ) {
-
-							v0 = parseVector( lp );
-							v1 = parseVector( lp );
-							v2 = parseVector( lp );
-
-						} else {
-
-							v2 = parseVector( lp );
-							v1 = parseVector( lp );
-							v0 = parseVector( lp );
+								}
 
-						}
+							}
 
-						tempVec0.subVectors( v1, v0 );
-						tempVec1.subVectors( v2, v1 );
-						faceNormal = new THREE.Vector3()
-							.crossVectors( tempVec0, tempVec1 )
-							.normalize();
-
-						triangles.push( {
-							material: material,
-							colourCode: material.userData.code,
-							v0: v0,
-							v1: v1,
-							v2: v2,
-							faceNormal: faceNormal,
-							n0: null,
-							n1: null,
-							n2: null
-						} );
+							subobjects.push( {
+								material: material,
+								matrix: matrix,
+								fileName: fileName,
+								originalFileName: fileName,
+								locationState: LDrawLoader.FILE_LOCATION_AS_IS,
+								url: null,
+								triedLowerCase: false,
+								inverted: bfcInverted !== currentParseScope.inverted,
+								startingConstructionStep: startingConstructionStep
+							} );
+							bfcInverted = false;
+							break;
+							// Line type 2: Line segment
+
+						case '2':
+							var material = parseColourCode( lp, true );
+							var segment = {
+								material: material.userData.edgeMaterial,
+								colourCode: material.userData.code,
+								v0: parseVector( lp ),
+								v1: parseVector( lp )
+							};
+							lineSegments.push( segment );
+							break;
+							// Line type 5: Conditional Line segment
+
+						case '5':
+							var material = parseColourCode( lp, true );
+							var segment = {
+								material: material.userData.edgeMaterial.userData.conditionalEdgeMaterial,
+								colourCode: material.userData.code,
+								v0: parseVector( lp ),
+								v1: parseVector( lp ),
+								c0: parseVector( lp ),
+								c1: parseVector( lp )
+							};
+							conditionalSegments.push( segment );
+							break;
+							// Line type 3: Triangle
+
+						case '3':
+							var material = parseColourCode( lp );
+							var inverted = currentParseScope.inverted;
+							var ccw = bfcCCW !== inverted;
+							var doubleSided = ! bfcCertified || ! bfcCull;
+							var v0, v1, v2, faceNormal;
+
+							if ( ccw === true ) {
+
+								v0 = parseVector( lp );
+								v1 = parseVector( lp );
+								v2 = parseVector( lp );
+
+							} else {
+
+								v2 = parseVector( lp );
+								v1 = parseVector( lp );
+								v0 = parseVector( lp );
 
-						if ( doubleSided === true ) {
+							}
 
+							tempVec0.subVectors( v1, v0 );
+							tempVec1.subVectors( v2, v1 );
+							faceNormal = new THREE.Vector3().crossVectors( tempVec0, tempVec1 ).normalize();
 							triangles.push( {
 								material: material,
 								colourCode: material.userData.code,
 								v0: v0,
-								v1: v2,
-								v2: v1,
+								v1: v1,
+								v2: v2,
 								faceNormal: faceNormal,
 								n0: null,
 								n1: null,
 								n2: null
 							} );
 
-						}
-
-						break;
+							if ( doubleSided === true ) {
 
-					// Line type 4: Quadrilateral
-					case '4':
+								triangles.push( {
+									material: material,
+									colourCode: material.userData.code,
+									v0: v0,
+									v1: v2,
+									v2: v1,
+									faceNormal: faceNormal,
+									n0: null,
+									n1: null,
+									n2: null
+								} );
 
-						var material = parseColourCode( lp );
-
-						var inverted = currentParseScope.inverted;
-						var ccw = bfcCCW !== inverted;
-						var doubleSided = ! bfcCertified || ! bfcCull;
-						var v0, v1, v2, v3, faceNormal;
-
-						if ( ccw === true ) {
+							}
 
-							v0 = parseVector( lp );
-							v1 = parseVector( lp );
-							v2 = parseVector( lp );
-							v3 = parseVector( lp );
+							break;
+							// Line type 4: Quadrilateral
 
-						} else {
+						case '4':
+							var material = parseColourCode( lp );
+							var inverted = currentParseScope.inverted;
+							var ccw = bfcCCW !== inverted;
+							var doubleSided = ! bfcCertified || ! bfcCull;
+							var v0, v1, v2, v3, faceNormal;
 
-							v3 = parseVector( lp );
-							v2 = parseVector( lp );
-							v1 = parseVector( lp );
-							v0 = parseVector( lp );
+							if ( ccw === true ) {
 
-						}
+								v0 = parseVector( lp );
+								v1 = parseVector( lp );
+								v2 = parseVector( lp );
+								v3 = parseVector( lp );
 
-						tempVec0.subVectors( v1, v0 );
-						tempVec1.subVectors( v2, v1 );
-						faceNormal = new THREE.Vector3()
-							.crossVectors( tempVec0, tempVec1 )
-							.normalize();
-
-						triangles.push( {
-							material: material,
-							colourCode: material.userData.code,
-							v0: v0,
-							v1: v1,
-							v2: v2,
-							faceNormal: faceNormal,
-							n0: null,
-							n1: null,
-							n2: null
-						} );
+							} else {
 
-						triangles.push( {
-							material: material,
-							colourCode: material.userData.code,
-							v0: v0,
-							v1: v2,
-							v2: v3,
-							faceNormal: faceNormal,
-							n0: null,
-							n1: null,
-							n2: null
-						} );
+								v3 = parseVector( lp );
+								v2 = parseVector( lp );
+								v1 = parseVector( lp );
+								v0 = parseVector( lp );
 
-						if ( doubleSided === true ) {
+							}
 
+							tempVec0.subVectors( v1, v0 );
+							tempVec1.subVectors( v2, v1 );
+							faceNormal = new THREE.Vector3().crossVectors( tempVec0, tempVec1 ).normalize();
 							triangles.push( {
 								material: material,
 								colourCode: material.userData.code,
 								v0: v0,
-								v1: v2,
-								v2: v1,
+								v1: v1,
+								v2: v2,
 								faceNormal: faceNormal,
 								n0: null,
 								n1: null,
 								n2: null
 							} );
-
 							triangles.push( {
 								material: material,
 								colourCode: material.userData.code,
 								v0: v0,
-								v1: v3,
-								v2: v2,
+								v1: v2,
+								v2: v3,
 								faceNormal: faceNormal,
 								n0: null,
 								n1: null,
 								n2: null
 							} );
 
-						}
+							if ( doubleSided === true ) {
+
+								triangles.push( {
+									material: material,
+									colourCode: material.userData.code,
+									v0: v0,
+									v1: v2,
+									v2: v1,
+									faceNormal: faceNormal,
+									n0: null,
+									n1: null,
+									n2: null
+								} );
+								triangles.push( {
+									material: material,
+									colourCode: material.userData.code,
+									v0: v0,
+									v1: v3,
+									v2: v2,
+									faceNormal: faceNormal,
+									n0: null,
+									n1: null,
+									n2: null
+								} );
 
-						break;
+							}
 
-					default:
-						throw 'LDrawLoader: Unknown line type "' + lineType + '"' + lp.getLineNumberString() + '.';
-						break;
+							break;
 
-				}
+						default:
+							throw 'LDrawLoader: Unknown line type "' + lineType + '"' + lp.getLineNumberString() + '.';
+							break;
 
-			}
+					}
 
-			if ( parsingEmbeddedFiles ) {
+				}
 
-				this.subobjectCache[ currentEmbeddedFileName.toLowerCase() ] = currentEmbeddedText;
+				if ( parsingEmbeddedFiles ) {
 
-			}
+					this.subobjectCache[ currentEmbeddedFileName.toLowerCase() ] = currentEmbeddedText;
 
-			currentParseScope.category = category;
-			currentParseScope.keywords = keywords;
-			currentParseScope.subobjects = subobjects;
-			currentParseScope.numSubobjects = subobjects.length;
-			currentParseScope.subobjectIndex = 0;
+				}
 
-		},
+				currentParseScope.category = category;
+				currentParseScope.keywords = keywords;
+				currentParseScope.subobjects = subobjects;
+				currentParseScope.numSubobjects = subobjects.length;
+				currentParseScope.subobjectIndex = 0;
 
-		computeConstructionSteps: function ( model ) {
+			},
+			computeConstructionSteps: function ( model ) {
 
-			// Sets userdata.constructionStep number in Group objects and userData.numConstructionSteps number in the root Group object.
+				// Sets userdata.constructionStep number in THREE.Group objects and userData.numConstructionSteps number in the root THREE.Group object.
+				var stepNumber = 0;
+				model.traverse( c => {
 
-			var stepNumber = 0;
+					if ( c.isGroup ) {
 
-			model.traverse( c => {
+						if ( c.userData.startingConstructionStep ) {
 
-				if ( c.isGroup ) {
+							stepNumber ++;
 
-					if ( c.userData.startingConstructionStep ) {
+						}
 
-						stepNumber ++;
+						c.userData.constructionStep = stepNumber;
 
 					}
 
-					c.userData.constructionStep = stepNumber;
-
-				}
-
-			} );
-
-			model.userData.numConstructionSteps = stepNumber + 1;
+				} );
+				model.userData.numConstructionSteps = stepNumber + 1;
 
-		},
+			},
+			processObject: function ( text, onProcessed, subobject, url ) {
 
-		processObject: function ( text, onProcessed, subobject, url ) {
+				var scope = this;
+				var parseScope = scope.newParseScopeLevel();
+				parseScope.url = url;
+				var parentParseScope = scope.getParentParseScope(); // Set current matrix
 
-			var scope = this;
+				if ( subobject ) {
 
-			var parseScope = scope.newParseScopeLevel();
-			parseScope.url = url;
+					parseScope.currentMatrix.multiplyMatrices( parentParseScope.currentMatrix, subobject.matrix );
+					parseScope.matrix.copy( subobject.matrix );
+					parseScope.inverted = subobject.inverted;
+					parseScope.startingConstructionStep = subobject.startingConstructionStep;
 
-			var parentParseScope = scope.getParentParseScope();
+				} // Add to cache
 
-			// Set current matrix
-			if ( subobject ) {
 
-				parseScope.currentMatrix.multiplyMatrices( parentParseScope.currentMatrix, subobject.matrix );
-				parseScope.matrix.copy( subobject.matrix );
-				parseScope.inverted = subobject.inverted;
-				parseScope.startingConstructionStep = subobject.startingConstructionStep;
+				var currentFileName = parentParseScope.currentFileName;
 
-			}
+				if ( currentFileName !== null ) {
 
-			// Add to cache
-			var currentFileName = parentParseScope.currentFileName;
-			if ( currentFileName !== null ) {
+					currentFileName = parentParseScope.currentFileName.toLowerCase();
 
-				currentFileName = parentParseScope.currentFileName.toLowerCase();
+				}
 
-			}
+				if ( scope.subobjectCache[ currentFileName ] === undefined ) {
 
-			if ( scope.subobjectCache[ currentFileName ] === undefined ) {
+					scope.subobjectCache[ currentFileName ] = text;
 
-				scope.subobjectCache[ currentFileName ] = text;
+				} // Parse the object (returns a THREE.Group)
 
-			}
 
+				scope.objectParse( text );
+				var finishedCount = 0;
+				onSubobjectFinish();
 
-			// Parse the object (returns a THREE.Group)
-			scope.objectParse( text );
-			var finishedCount = 0;
-			onSubobjectFinish();
+				function onSubobjectFinish() {
 
-			function onSubobjectFinish() {
+					finishedCount ++;
 
-				finishedCount ++;
+					if ( finishedCount === parseScope.subobjects.length + 1 ) {
 
-				if ( finishedCount === parseScope.subobjects.length + 1 ) {
+						finalizeObject();
 
-					finalizeObject();
+					} else {
 
-				} else {
+						// Once the previous subobject has finished we can start processing the next one in the list.
+						// The subobject processing shares scope in processing so it's important that they be loaded serially
+						// to avoid race conditions.
+						// Promise.resolve is used as an approach to asynchronously schedule a task _before_ this frame ends to
+						// avoid stack overflow exceptions when loading many subobjects from the cache. RequestAnimationFrame
+						// will work but causes the load to happen after the next frame which causes the load to take significantly longer.
+						var subobject = parseScope.subobjects[ parseScope.subobjectIndex ];
+						Promise.resolve().then( function () {
 
-					// Once the previous subobject has finished we can start processing the next one in the list.
-					// The subobject processing shares scope in processing so it's important that they be loaded serially
-					// to avoid race conditions.
-					// Promise.resolve is used as an approach to asynchronously schedule a task _before_ this frame ends to
-					// avoid stack overflow exceptions when loading many subobjects from the cache. RequestAnimationFrame
-					// will work but causes the load to happen after the next frame which causes the load to take significantly longer.
-					var subobject = parseScope.subobjects[ parseScope.subobjectIndex ];
-					Promise.resolve().then( function () {
+							loadSubobject( subobject );
 
-						loadSubobject( subobject );
+						} );
+						parseScope.subobjectIndex ++;
 
-					} );
-					parseScope.subobjectIndex ++;
+					}
 
 				}
 
-			}
+				function finalizeObject() {
 
-			function finalizeObject() {
+					if ( scope.smoothNormals && parseScope.type === 'Part' ) {
 
-				if ( scope.smoothNormals && parseScope.type === 'Part' ) {
+						smoothNormals( parseScope.triangles, parseScope.lineSegments );
 
-					smoothNormals( parseScope.triangles, parseScope.lineSegments );
+					}
 
-				}
+					var isRoot = ! parentParseScope.isFromParse;
 
-				var isRoot = ! parentParseScope.isFromParse;
-				if ( scope.separateObjects && ! isPrimitiveType( parseScope.type ) || isRoot ) {
+					if ( scope.separateObjects && ! isPrimitiveType( parseScope.type ) || isRoot ) {
 
-					const objGroup = parseScope.groupObject;
+						const objGroup = parseScope.groupObject;
 
-					if ( parseScope.triangles.length > 0 ) {
+						if ( parseScope.triangles.length > 0 ) {
 
-						objGroup.add( createObject( parseScope.triangles, 3 ) );
+							objGroup.add( createObject( parseScope.triangles, 3 ) );
 
-					}
+						}
 
-					if ( parseScope.lineSegments.length > 0 ) {
+						if ( parseScope.lineSegments.length > 0 ) {
 
-						objGroup.add( createObject( parseScope.lineSegments, 2 ) );
+							objGroup.add( createObject( parseScope.lineSegments, 2 ) );
 
-					}
+						}
 
-					if ( parseScope.conditionalSegments.length > 0 ) {
+						if ( parseScope.conditionalSegments.length > 0 ) {
 
-						objGroup.add( createObject( parseScope.conditionalSegments, 2, true ) );
+							objGroup.add( createObject( parseScope.conditionalSegments, 2, true ) );
 
-					}
+						}
 
-					if ( parentParseScope.groupObject ) {
+						if ( parentParseScope.groupObject ) {
 
-						objGroup.name = parseScope.fileName;
-						objGroup.userData.category = parseScope.category;
-						objGroup.userData.keywords = parseScope.keywords;
-						parseScope.matrix.decompose( objGroup.position, objGroup.quaternion, objGroup.scale );
+							objGroup.name = parseScope.fileName;
+							objGroup.userData.category = parseScope.category;
+							objGroup.userData.keywords = parseScope.keywords;
+							parseScope.matrix.decompose( objGroup.position, objGroup.quaternion, objGroup.scale );
+							parentParseScope.groupObject.add( objGroup );
 
-						parentParseScope.groupObject.add( objGroup );
+						}
 
-					}
+					} else {
 
-				} else {
+						var separateObjects = scope.separateObjects;
+						var parentLineSegments = parentParseScope.lineSegments;
+						var parentConditionalSegments = parentParseScope.conditionalSegments;
+						var parentTriangles = parentParseScope.triangles;
+						var lineSegments = parseScope.lineSegments;
+						var conditionalSegments = parseScope.conditionalSegments;
+						var triangles = parseScope.triangles;
 
-					var separateObjects = scope.separateObjects;
-					var parentLineSegments = parentParseScope.lineSegments;
-					var parentConditionalSegments = parentParseScope.conditionalSegments;
-					var parentTriangles = parentParseScope.triangles;
+						for ( var i = 0, l = lineSegments.length; i < l; i ++ ) {
 
-					var lineSegments = parseScope.lineSegments;
-					var conditionalSegments = parseScope.conditionalSegments;
-					var triangles = parseScope.triangles;
+							var ls = lineSegments[ i ];
 
-					for ( var i = 0, l = lineSegments.length; i < l; i ++ ) {
+							if ( separateObjects ) {
 
-						var ls = lineSegments[ i ];
+								ls.v0.applyMatrix4( parseScope.matrix );
+								ls.v1.applyMatrix4( parseScope.matrix );
 
-						if ( separateObjects ) {
+							}
 
-							ls.v0.applyMatrix4( parseScope.matrix );
-							ls.v1.applyMatrix4( parseScope.matrix );
+							parentLineSegments.push( ls );
 
 						}
 
-						parentLineSegments.push( ls );
+						for ( var i = 0, l = conditionalSegments.length; i < l; i ++ ) {
 
-					}
+							var os = conditionalSegments[ i ];
 
-					for ( var i = 0, l = conditionalSegments.length; i < l; i ++ ) {
+							if ( separateObjects ) {
 
-						var os = conditionalSegments[ i ];
+								os.v0.applyMatrix4( parseScope.matrix );
+								os.v1.applyMatrix4( parseScope.matrix );
+								os.c0.applyMatrix4( parseScope.matrix );
+								os.c1.applyMatrix4( parseScope.matrix );
 
-						if ( separateObjects ) {
+							}
 
-							os.v0.applyMatrix4( parseScope.matrix );
-							os.v1.applyMatrix4( parseScope.matrix );
-							os.c0.applyMatrix4( parseScope.matrix );
-							os.c1.applyMatrix4( parseScope.matrix );
+							parentConditionalSegments.push( os );
 
 						}
 
-						parentConditionalSegments.push( os );
-
-					}
+						for ( var i = 0, l = triangles.length; i < l; i ++ ) {
 
-					for ( var i = 0, l = triangles.length; i < l; i ++ ) {
+							var tri = triangles[ i ];
 
-						var tri = triangles[ i ];
+							if ( separateObjects ) {
 
-						if ( separateObjects ) {
+								tri.v0 = tri.v0.clone().applyMatrix4( parseScope.matrix );
+								tri.v1 = tri.v1.clone().applyMatrix4( parseScope.matrix );
+								tri.v2 = tri.v2.clone().applyMatrix4( parseScope.matrix );
+								tempVec0.subVectors( tri.v1, tri.v0 );
+								tempVec1.subVectors( tri.v2, tri.v1 );
+								tri.faceNormal.crossVectors( tempVec0, tempVec1 ).normalize();
 
-							tri.v0 = tri.v0.clone().applyMatrix4( parseScope.matrix );
-							tri.v1 = tri.v1.clone().applyMatrix4( parseScope.matrix );
-							tri.v2 = tri.v2.clone().applyMatrix4( parseScope.matrix );
+							}
 
-							tempVec0.subVectors( tri.v1, tri.v0 );
-							tempVec1.subVectors( tri.v2, tri.v1 );
-							tri.faceNormal.crossVectors( tempVec0, tempVec1 ).normalize();
+							parentTriangles.push( tri );
 
 						}
 
-						parentTriangles.push( tri );
-
 					}
 
-				}
+					scope.removeScopeLevel(); // If it is root object, compute construction steps
 
-				scope.removeScopeLevel();
+					if ( ! parentParseScope.isFromParse ) {
 
-				// If it is root object, compute construction steps
-				if ( ! parentParseScope.isFromParse ) {
+						scope.computeConstructionSteps( parseScope.groupObject );
 
-					scope.computeConstructionSteps( parseScope.groupObject );
+					}
 
-				}
+					if ( onProcessed ) {
 
-				if ( onProcessed ) {
+						onProcessed( parseScope.groupObject );
 
-					onProcessed( parseScope.groupObject );
+					}
 
 				}
 
-			}
+				function loadSubobject( subobject ) {
 
-			function loadSubobject( subobject ) {
+					parseScope.mainColourCode = subobject.material.userData.code;
+					parseScope.mainEdgeColourCode = subobject.material.userData.edgeMaterial.userData.code;
+					parseScope.currentFileName = subobject.originalFileName; // If subobject was cached previously, use the cached one
 
-				parseScope.mainColourCode = subobject.material.userData.code;
-				parseScope.mainEdgeColourCode = subobject.material.userData.edgeMaterial.userData.code;
-				parseScope.currentFileName = subobject.originalFileName;
+					var cached = scope.subobjectCache[ subobject.originalFileName.toLowerCase() ];
 
+					if ( cached ) {
 
-				// If subobject was cached previously, use the cached one
-				var cached = scope.subobjectCache[ subobject.originalFileName.toLowerCase() ];
-				if ( cached ) {
+						scope.processObject( cached, function ( subobjectGroup ) {
 
-					scope.processObject( cached, function ( subobjectGroup ) {
+							onSubobjectLoaded( subobjectGroup, subobject );
+							onSubobjectFinish();
 
-						onSubobjectLoaded( subobjectGroup, subobject );
-						onSubobjectFinish();
+						}, subobject, url );
+						return;
 
-					}, subobject, url );
+					} // Adjust file name to locate the subobject file path in standard locations (always under directory scope.path)
+					// Update also subobject.locationState for the next try if this load fails.
 
-					return;
 
-				}
+					var subobjectURL = subobject.fileName;
+					var newLocationState = LDrawLoader.FILE_LOCATION_NOT_FOUND;
 
-				// Adjust file name to locate the subobject file path in standard locations (always under directory scope.path)
-				// Update also subobject.locationState for the next try if this load fails.
-				var subobjectURL = subobject.fileName;
-				var newLocationState = LDrawLoader.FILE_LOCATION_NOT_FOUND;
+					switch ( subobject.locationState ) {
 
-				switch ( subobject.locationState ) {
+						case LDrawLoader.FILE_LOCATION_AS_IS:
+							newLocationState = subobject.locationState + 1;
+							break;
 
-					case LDrawLoader.FILE_LOCATION_AS_IS:
-						newLocationState = subobject.locationState + 1;
-						break;
+						case LDrawLoader.FILE_LOCATION_TRY_PARTS:
+							subobjectURL = 'parts/' + subobjectURL;
+							newLocationState = subobject.locationState + 1;
+							break;
 
-					case LDrawLoader.FILE_LOCATION_TRY_PARTS:
-						subobjectURL = 'parts/' + subobjectURL;
-						newLocationState = subobject.locationState + 1;
-						break;
+						case LDrawLoader.FILE_LOCATION_TRY_P:
+							subobjectURL = 'p/' + subobjectURL;
+							newLocationState = subobject.locationState + 1;
+							break;
 
-					case LDrawLoader.FILE_LOCATION_TRY_P:
-						subobjectURL = 'p/' + subobjectURL;
-						newLocationState = subobject.locationState + 1;
-						break;
+						case LDrawLoader.FILE_LOCATION_TRY_MODELS:
+							subobjectURL = 'models/' + subobjectURL;
+							newLocationState = subobject.locationState + 1;
+							break;
 
-					case LDrawLoader.FILE_LOCATION_TRY_MODELS:
-						subobjectURL = 'models/' + subobjectURL;
-						newLocationState = subobject.locationState + 1;
-						break;
-
-					case LDrawLoader.FILE_LOCATION_TRY_RELATIVE:
-						subobjectURL = url.substring( 0, url.lastIndexOf( '/' ) + 1 ) + subobjectURL;
-						newLocationState = subobject.locationState + 1;
-						break;
+						case LDrawLoader.FILE_LOCATION_TRY_RELATIVE:
+							subobjectURL = url.substring( 0, url.lastIndexOf( '/' ) + 1 ) + subobjectURL;
+							newLocationState = subobject.locationState + 1;
+							break;
 
-					case LDrawLoader.FILE_LOCATION_TRY_ABSOLUTE:
+						case LDrawLoader.FILE_LOCATION_TRY_ABSOLUTE:
+							if ( subobject.triedLowerCase ) {
 
-						if ( subobject.triedLowerCase ) {
+								// Try absolute path
+								newLocationState = LDrawLoader.FILE_LOCATION_NOT_FOUND;
 
-							// Try absolute path
-							newLocationState = LDrawLoader.FILE_LOCATION_NOT_FOUND;
+							} else {
 
-						} else {
-
-							// Next attempt is lower case
-							subobject.fileName = subobject.fileName.toLowerCase();
-							subobjectURL = subobject.fileName;
-							subobject.triedLowerCase = true;
-							newLocationState = LDrawLoader.FILE_LOCATION_AS_IS;
-
-						}
+								// Next attempt is lower case
+								subobject.fileName = subobject.fileName.toLowerCase();
+								subobjectURL = subobject.fileName;
+								subobject.triedLowerCase = true;
+								newLocationState = LDrawLoader.FILE_LOCATION_AS_IS;
 
-						break;
+							}
 
-					case LDrawLoader.FILE_LOCATION_NOT_FOUND:
+							break;
 
+						case LDrawLoader.FILE_LOCATION_NOT_FOUND:
 						// All location possibilities have been tried, give up loading this object
-						console.warn( 'LDrawLoader: Subobject "' + subobject.originalFileName + '" could not be found.' );
+							console.warn( 'LDrawLoader: Subobject "' + subobject.originalFileName + '" could not be found.' );
+							return;
 
-						return;
+					}
 
-				}
+					subobject.locationState = newLocationState;
+					subobject.url = subobjectURL; // Load the subobject
+					// Use another file loader here so we can keep track of the subobject information
+					// and use it when processing the next model.
 
-				subobject.locationState = newLocationState;
-				subobject.url = subobjectURL;
+					var fileLoader = new THREE.FileLoader( scope.manager );
+					fileLoader.setPath( scope.path );
+					fileLoader.setRequestHeader( scope.requestHeader );
+					fileLoader.setWithCredentials( scope.withCredentials );
+					fileLoader.load( subobjectURL, function ( text ) {
 
-				// Load the subobject
-				// Use another file loader here so we can keep track of the subobject information
-				// and use it when processing the next model.
-				var fileLoader = new THREE.FileLoader( scope.manager );
-				fileLoader.setPath( scope.path );
-				fileLoader.setRequestHeader( scope.requestHeader );
-				fileLoader.setWithCredentials( scope.withCredentials );
-				fileLoader.load( subobjectURL, function ( text ) {
+						scope.processObject( text, function ( subobjectGroup ) {
 
-					scope.processObject( text, function ( subobjectGroup ) {
+							onSubobjectLoaded( subobjectGroup, subobject );
+							onSubobjectFinish();
 
-						onSubobjectLoaded( subobjectGroup, subobject );
-						onSubobjectFinish();
+						}, subobject, url );
 
-					}, subobject, url );
+					}, undefined, function ( err ) {
 
-				}, undefined, function ( err ) {
+						onSubobjectError( err, subobject );
 
-					onSubobjectError( err, subobject );
+					}, subobject );
 
-				}, subobject );
+				}
 
-			}
+				function onSubobjectLoaded( subobjectGroup, subobject ) {
 
-			function onSubobjectLoaded( subobjectGroup, subobject ) {
+					if ( subobjectGroup === null ) {
 
-				if ( subobjectGroup === null ) {
+						// Try to reload
+						loadSubobject( subobject );
+						return;
 
-					// Try to reload
-					loadSubobject( subobject );
-					return;
+					}
 
-				}
+					scope.fileMap[ subobject.originalFileName ] = subobject.url;
 
-				scope.fileMap[ subobject.originalFileName ] = subobject.url;
+				}
 
-			}
+				function onSubobjectError( err, subobject ) {
 
-			function onSubobjectError( err, subobject ) {
+					// Retry download from a different default possible location
+					loadSubobject( subobject );
 
-				// Retry download from a different default possible location
-				loadSubobject( subobject );
+				}
 
 			}
+		} );
+		return LDrawLoader;
 
-		}
-
-	} );
+	}();
 
-	return LDrawLoader;
+	THREE.LDrawLoader = LDrawLoader;
 
 } )();

+ 130 - 0
examples/js/loaders/LUT3dlLoader.js

@@ -0,0 +1,130 @@
+( function () {
+
+	// http://download.autodesk.com/us/systemdocs/help/2011/lustre/index.html?url=./files/WSc4e151a45a3b785a24c3d9a411df9298473-7ffd.htm,topicNumber=d0e9492
+	class LUT3dlLoader extends THREE.Loader {
+
+		load( url, onLoad, onProgress, onError ) {
+
+			const loader = new THREE.FileLoader( this.manager );
+			loader.setPath( this.path );
+			loader.setResponseType( 'text' );
+			loader.load( url, text => {
+
+				try {
+
+					onLoad( this.parse( text ) );
+
+				} catch ( e ) {
+
+					if ( onError ) {
+
+						onError( e );
+
+					} else {
+
+						console.error( e );
+
+					}
+
+					this.manager.itemError( url );
+
+				}
+
+			}, onProgress, onError );
+
+		}
+
+		parse( str ) {
+
+			// remove empty lines and comment lints
+			str = str.replace( /^#.*?(\n|\r)/gm, '' ).replace( /^\s*?(\n|\r)/gm, '' ).trim();
+			const lines = str.split( /[\n\r]+/g ); // first line is the positions on the grid that are provided by the LUT
+
+			const gridLines = lines[ 0 ].trim().split( /\s+/g ).map( e => parseFloat( e ) );
+			const gridStep = gridLines[ 1 ] - gridLines[ 0 ];
+			const size = gridLines.length;
+
+			for ( let i = 1, l = gridLines.length; i < l; i ++ ) {
+
+				if ( gridStep !== gridLines[ i ] - gridLines[ i - 1 ] ) {
+
+					throw new Error( 'LUT3dlLoader: Inconsistent grid size not supported.' );
+
+				}
+
+			}
+
+			const dataArray = new Array( size * size * size * 3 );
+			let index = 0;
+			let maxOutputValue = 0.0;
+
+			for ( let i = 1, l = lines.length; i < l; i ++ ) {
+
+				const line = lines[ i ].trim();
+				const split = line.split( /\s/g );
+				const r = parseFloat( split[ 0 ] );
+				const g = parseFloat( split[ 1 ] );
+				const b = parseFloat( split[ 2 ] );
+				maxOutputValue = Math.max( maxOutputValue, r, g, b );
+				const bLayer = index % size;
+				const gLayer = Math.floor( index / size ) % size;
+				const rLayer = Math.floor( index / ( size * size ) ) % size; // b grows first, then g, then r
+
+				const pixelIndex = bLayer * size * size + gLayer * size + rLayer;
+				dataArray[ 3 * pixelIndex + 0 ] = r;
+				dataArray[ 3 * pixelIndex + 1 ] = g;
+				dataArray[ 3 * pixelIndex + 2 ] = b;
+				index += 1;
+
+			} // Find the apparent bit depth of the stored RGB values and scale the
+			// values to [ 0, 255 ].
+
+
+			const bits = Math.ceil( Math.log2( maxOutputValue ) );
+			const maxBitValue = Math.pow( 2.0, bits );
+
+			for ( let i = 0, l = dataArray.length; i < l; i ++ ) {
+
+				const val = dataArray[ i ];
+				dataArray[ i ] = 255 * val / maxBitValue;
+
+			}
+
+			const data = new Uint8Array( dataArray );
+			const texture = new THREE.DataTexture();
+			texture.image.data = data;
+			texture.image.width = size;
+			texture.image.height = size * size;
+			texture.format = THREE.RGBFormat;
+			texture.type = THREE.UnsignedByteType;
+			texture.magFilter = THREE.LinearFilter;
+			texture.minFilter = THREE.LinearFilter;
+			texture.wrapS = THREE.ClampToEdgeWrapping;
+			texture.wrapT = THREE.ClampToEdgeWrapping;
+			texture.generateMipmaps = false;
+			const texture3D = new THREE.DataTexture3D();
+			texture3D.image.data = data;
+			texture3D.image.width = size;
+			texture3D.image.height = size;
+			texture3D.image.depth = size;
+			texture3D.format = THREE.RGBFormat;
+			texture3D.type = THREE.UnsignedByteType;
+			texture3D.magFilter = THREE.LinearFilter;
+			texture3D.minFilter = THREE.LinearFilter;
+			texture3D.wrapS = THREE.ClampToEdgeWrapping;
+			texture3D.wrapT = THREE.ClampToEdgeWrapping;
+			texture3D.wrapR = THREE.ClampToEdgeWrapping;
+			texture3D.generateMipmaps = false;
+			return {
+				size,
+				texture,
+				texture3D
+			};
+
+		}
+
+	}
+
+	THREE.LUT3dlLoader = LUT3dlLoader;
+
+} )();

+ 139 - 0
examples/js/loaders/LUTCubeLoader.js

@@ -0,0 +1,139 @@
+( function () {
+
+	// https://wwwimages2.adobe.com/content/dam/acom/en/products/speedgrade/cc/pdfs/cube-lut-specification-1.0.pdf
+	class LUTCubeLoader extends THREE.Loader {
+
+		load( url, onLoad, onProgress, onError ) {
+
+			const loader = new THREE.FileLoader( this.manager );
+			loader.setPath( this.path );
+			loader.setResponseType( 'text' );
+			loader.load( url, text => {
+
+				try {
+
+					onLoad( this.parse( text ) );
+
+				} catch ( e ) {
+
+					if ( onError ) {
+
+						onError( e );
+
+					} else {
+
+						console.error( e );
+
+					}
+
+					this.manager.itemError( url );
+
+				}
+
+			}, onProgress, onError );
+
+		}
+
+		parse( str ) {
+
+			// Remove empty lines and comments
+			str = str.replace( /^#.*?(\n|\r)/gm, '' ).replace( /^\s*?(\n|\r)/gm, '' ).trim();
+			let title = null;
+			let size = null;
+			const domainMin = new THREE.Vector3( 0, 0, 0 );
+			const domainMax = new THREE.Vector3( 1, 1, 1 );
+			const lines = str.split( /[\n\r]+/g );
+			let data = null;
+			let currIndex = 0;
+
+			for ( let i = 0, l = lines.length; i < l; i ++ ) {
+
+				const line = lines[ i ].trim();
+				const split = line.split( /\s/g );
+
+				switch ( split[ 0 ] ) {
+
+					case 'TITLE':
+						title = line.substring( 7, line.length - 1 );
+						break;
+
+					case 'LUT_3D_SIZE':
+					// TODO: A .CUBE LUT file specifies floating point values and could be represented with
+					// more precision than can be captured with Uint8Array.
+						const sizeToken = split[ 1 ];
+						size = parseFloat( sizeToken );
+						data = new Uint8Array( size * size * size * 3 );
+						break;
+
+					case 'DOMAIN_MIN':
+						domainMin.x = parseFloat( split[ 1 ] );
+						domainMin.y = parseFloat( split[ 2 ] );
+						domainMin.z = parseFloat( split[ 3 ] );
+						break;
+
+					case 'DOMAIN_MAX':
+						domainMax.x = parseFloat( split[ 1 ] );
+						domainMax.y = parseFloat( split[ 2 ] );
+						domainMax.z = parseFloat( split[ 3 ] );
+						break;
+
+					default:
+						const r = parseFloat( split[ 0 ] );
+						const g = parseFloat( split[ 1 ] );
+						const b = parseFloat( split[ 2 ] );
+
+						if ( r > 1.0 || r < 0.0 || g > 1.0 || g < 0.0 || b > 1.0 || b < 0.0 ) {
+
+							throw new Error( 'LUTCubeLoader : Non normalized values not supported.' );
+
+						}
+
+						data[ currIndex + 0 ] = r * 255;
+						data[ currIndex + 1 ] = g * 255;
+						data[ currIndex + 2 ] = b * 255;
+						currIndex += 3;
+
+				}
+
+			}
+
+			const texture = new THREE.DataTexture();
+			texture.image.data = data;
+			texture.image.width = size;
+			texture.image.height = size * size;
+			texture.format = THREE.RGBFormat;
+			texture.type = THREE.UnsignedByteType;
+			texture.magFilter = THREE.LinearFilter;
+			texture.minFilter = THREE.LinearFilter;
+			texture.wrapS = THREE.ClampToEdgeWrapping;
+			texture.wrapT = THREE.ClampToEdgeWrapping;
+			texture.generateMipmaps = false;
+			const texture3D = new THREE.DataTexture3D();
+			texture3D.image.data = data;
+			texture3D.image.width = size;
+			texture3D.image.height = size;
+			texture3D.image.depth = size;
+			texture3D.format = THREE.RGBFormat;
+			texture3D.type = THREE.UnsignedByteType;
+			texture3D.magFilter = THREE.LinearFilter;
+			texture3D.minFilter = THREE.LinearFilter;
+			texture3D.wrapS = THREE.ClampToEdgeWrapping;
+			texture3D.wrapT = THREE.ClampToEdgeWrapping;
+			texture3D.wrapR = THREE.ClampToEdgeWrapping;
+			texture3D.generateMipmaps = false;
+			return {
+				title,
+				size,
+				domainMin,
+				domainMax,
+				texture,
+				texture3D
+			};
+
+		}
+
+	}
+
+	THREE.LUTCubeLoader = LUTCubeLoader;
+
+} )();

+ 958 - 0
examples/js/loaders/LWOLoader.js

@@ -0,0 +1,958 @@
+( function () {
+
+	/**
+ * @version 1.1.1
+ *
+ * @desc Load files in LWO3 and LWO2 format on Three.js
+ *
+ * LWO3 format specification:
+ * 	http://static.lightwave3d.com/sdk/2018/html/filefmts/lwo3.html
+ *
+ * LWO2 format specification:
+ * 	http://static.lightwave3d.com/sdk/2018/html/filefmts/lwo2.html
+ *
+ **/
+	var lwoTree;
+
+	var LWOLoader = function ( manager, parameters ) {
+
+		THREE.Loader.call( this, manager );
+		parameters = parameters || {};
+		this.resourcePath = parameters.resourcePath !== undefined ? parameters.resourcePath : '';
+
+	};
+
+	LWOLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+		constructor: LWOLoader,
+		load: function ( url, onLoad, onProgress, onError ) {
+
+			var scope = this;
+			var path = scope.path === '' ? extractParentUrl( url, 'Objects' ) : scope.path; // give the mesh a default name based on the filename
+
+			var modelName = url.split( path ).pop().split( '.' )[ 0 ];
+			var loader = new THREE.FileLoader( this.manager );
+			loader.setPath( scope.path );
+			loader.setResponseType( 'arraybuffer' );
+			loader.load( url, function ( buffer ) {
+
+				// console.time( 'Total parsing: ' );
+				try {
+
+					onLoad( scope.parse( buffer, path, modelName ) );
+
+				} catch ( e ) {
+
+					if ( onError ) {
+
+						onError( e );
+
+					} else {
+
+						console.error( e );
+
+					}
+
+					scope.manager.itemError( url );
+
+				} // console.timeEnd( 'Total parsing: ' );
+
+			}, onProgress, onError );
+
+		},
+		parse: function ( iffBuffer, path, modelName ) {
+
+			lwoTree = new THREE.IFFParser().parse( iffBuffer ); // console.log( 'lwoTree', lwoTree );
+
+			var textureLoader = new THREE.TextureLoader( this.manager ).setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin );
+			return new LWOTreeParser( textureLoader ).parse( modelName );
+
+		}
+	} ); // Parse the lwoTree object
+
+	function LWOTreeParser( textureLoader ) {
+
+		this.textureLoader = textureLoader;
+
+	}
+
+	LWOTreeParser.prototype = {
+		constructor: LWOTreeParser,
+		parse: function ( modelName ) {
+
+			this.materials = new MaterialParser( this.textureLoader ).parse();
+			this.defaultLayerName = modelName;
+			this.meshes = this.parseLayers();
+			return {
+				materials: this.materials,
+				meshes: this.meshes
+			};
+
+		},
+
+		parseLayers() {
+
+			// array of all meshes for building hierarchy
+			var meshes = []; // final array containing meshes with scene graph hierarchy set up
+
+			var finalMeshes = [];
+			var geometryParser = new GeometryParser();
+			var scope = this;
+			lwoTree.layers.forEach( function ( layer ) {
+
+				var geometry = geometryParser.parse( layer.geometry, layer );
+				var mesh = scope.parseMesh( geometry, layer );
+				meshes[ layer.number ] = mesh;
+				if ( layer.parent === - 1 ) finalMeshes.push( mesh ); else meshes[ layer.parent ].add( mesh );
+
+			} );
+			this.applyPivots( finalMeshes );
+			return finalMeshes;
+
+		},
+
+		parseMesh( geometry, layer ) {
+
+			var mesh;
+			var materials = this.getMaterials( geometry.userData.matNames, layer.geometry.type );
+			this.duplicateUVs( geometry, materials );
+			if ( layer.geometry.type === 'points' ) mesh = new THREE.Points( geometry, materials ); else if ( layer.geometry.type === 'lines' ) mesh = new THREE.LineSegments( geometry, materials ); else mesh = new THREE.Mesh( geometry, materials );
+			if ( layer.name ) mesh.name = layer.name; else mesh.name = this.defaultLayerName + '_layer_' + layer.number;
+			mesh.userData.pivot = layer.pivot;
+			return mesh;
+
+		},
+
+		// TODO: may need to be reversed in z to convert LWO to three.js coordinates
+		applyPivots( meshes ) {
+
+			meshes.forEach( function ( mesh ) {
+
+				mesh.traverse( function ( child ) {
+
+					var pivot = child.userData.pivot;
+					child.position.x += pivot[ 0 ];
+					child.position.y += pivot[ 1 ];
+					child.position.z += pivot[ 2 ];
+
+					if ( child.parent ) {
+
+						var parentPivot = child.parent.userData.pivot;
+						child.position.x -= parentPivot[ 0 ];
+						child.position.y -= parentPivot[ 1 ];
+						child.position.z -= parentPivot[ 2 ];
+
+					}
+
+				} );
+
+			} );
+
+		},
+
+		getMaterials( namesArray, type ) {
+
+			var materials = [];
+			var scope = this;
+			namesArray.forEach( function ( name, i ) {
+
+				materials[ i ] = scope.getMaterialByName( name );
+
+			} ); // convert materials to line or point mats if required
+
+			if ( type === 'points' || type === 'lines' ) {
+
+				materials.forEach( function ( mat, i ) {
+
+					var spec = {
+						color: mat.color
+					};
+
+					if ( type === 'points' ) {
+
+						spec.size = 0.1;
+						spec.map = mat.map;
+						spec.morphTargets = mat.morphTargets;
+						materials[ i ] = new THREE.PointsMaterial( spec );
+
+					} else if ( type === 'lines' ) {
+
+						materials[ i ] = new THREE.LineBasicMaterial( spec );
+
+					}
+
+				} );
+
+			} // if there is only one material, return that directly instead of array
+
+
+			var filtered = materials.filter( Boolean );
+			if ( filtered.length === 1 ) return filtered[ 0 ];
+			return materials;
+
+		},
+
+		getMaterialByName( name ) {
+
+			return this.materials.filter( function ( m ) {
+
+				return m.name === name;
+
+			} )[ 0 ];
+
+		},
+
+		// If the material has an aoMap, duplicate UVs
+		duplicateUVs( geometry, materials ) {
+
+			var duplicateUVs = false;
+
+			if ( ! Array.isArray( materials ) ) {
+
+				if ( materials.aoMap ) duplicateUVs = true;
+
+			} else {
+
+				materials.forEach( function ( material ) {
+
+					if ( material.aoMap ) duplicateUVs = true;
+
+				} );
+
+			}
+
+			if ( ! duplicateUVs ) return;
+			geometry.setAttribute( 'uv2', new THREE.BufferAttribute( geometry.attributes.uv.array, 2 ) );
+
+		}
+
+	};
+
+	function MaterialParser( textureLoader ) {
+
+		this.textureLoader = textureLoader;
+
+	}
+
+	MaterialParser.prototype = {
+		constructor: MaterialParser,
+		parse: function () {
+
+			var materials = [];
+			this.textures = {};
+
+			for ( var name in lwoTree.materials ) {
+
+				if ( lwoTree.format === 'LWO3' ) {
+
+					materials.push( this.parseMaterial( lwoTree.materials[ name ], name, lwoTree.textures ) );
+
+				} else if ( lwoTree.format === 'LWO2' ) {
+
+					materials.push( this.parseMaterialLwo2( lwoTree.materials[ name ], name, lwoTree.textures ) );
+
+				}
+
+			}
+
+			return materials;
+
+		},
+
+		parseMaterial( materialData, name, textures ) {
+
+			var params = {
+				name: name,
+				side: this.getSide( materialData.attributes ),
+				flatShading: this.getSmooth( materialData.attributes )
+			};
+			var connections = this.parseConnections( materialData.connections, materialData.nodes );
+			var maps = this.parseTextureNodes( connections.maps );
+			this.parseAttributeImageMaps( connections.attributes, textures, maps, materialData.maps );
+			var attributes = this.parseAttributes( connections.attributes, maps );
+			this.parseEnvMap( connections, maps, attributes );
+			params = Object.assign( maps, params );
+			params = Object.assign( params, attributes );
+			var materialType = this.getMaterialType( connections.attributes );
+			return new materialType( params );
+
+		},
+
+		parseMaterialLwo2( materialData, name
+			/*, textures*/
+		) {
+
+			var params = {
+				name: name,
+				side: this.getSide( materialData.attributes ),
+				flatShading: this.getSmooth( materialData.attributes )
+			};
+			var attributes = this.parseAttributes( materialData.attributes, {} );
+			params = Object.assign( params, attributes );
+			return new THREE.MeshPhongMaterial( params );
+
+		},
+
+		// Note: converting from left to right handed coords by switching x -> -x in vertices, and
+		// then switching mat THREE.FrontSide -> THREE.BackSide
+		// NB: this means that THREE.FrontSide and THREE.BackSide have been switched!
+		getSide( attributes ) {
+
+			if ( ! attributes.side ) return THREE.BackSide;
+
+			switch ( attributes.side ) {
+
+				case 0:
+				case 1:
+					return THREE.BackSide;
+
+				case 2:
+					return THREE.FrontSide;
+
+				case 3:
+					return THREE.DoubleSide;
+
+			}
+
+		},
+
+		getSmooth( attributes ) {
+
+			if ( ! attributes.smooth ) return true;
+			return ! attributes.smooth;
+
+		},
+
+		parseConnections( connections, nodes ) {
+
+			var materialConnections = {
+				maps: {}
+			};
+			var inputName = connections.inputName;
+			var inputNodeName = connections.inputNodeName;
+			var nodeName = connections.nodeName;
+			var scope = this;
+			inputName.forEach( function ( name, index ) {
+
+				if ( name === 'Material' ) {
+
+					var matNode = scope.getNodeByRefName( inputNodeName[ index ], nodes );
+					materialConnections.attributes = matNode.attributes;
+					materialConnections.envMap = matNode.fileName;
+					materialConnections.name = inputNodeName[ index ];
+
+				}
+
+			} );
+			nodeName.forEach( function ( name, index ) {
+
+				if ( name === materialConnections.name ) {
+
+					materialConnections.maps[ inputName[ index ] ] = scope.getNodeByRefName( inputNodeName[ index ], nodes );
+
+				}
+
+			} );
+			return materialConnections;
+
+		},
+
+		getNodeByRefName( refName, nodes ) {
+
+			for ( var name in nodes ) {
+
+				if ( nodes[ name ].refName === refName ) return nodes[ name ];
+
+			}
+
+		},
+
+		parseTextureNodes( textureNodes ) {
+
+			var maps = {};
+
+			for ( var name in textureNodes ) {
+
+				var node = textureNodes[ name ];
+				var path = node.fileName;
+				if ( ! path ) return;
+				var texture = this.loadTexture( path );
+				if ( node.widthWrappingMode !== undefined ) texture.wrapS = this.getWrappingType( node.widthWrappingMode );
+				if ( node.heightWrappingMode !== undefined ) texture.wrapT = this.getWrappingType( node.heightWrappingMode );
+
+				switch ( name ) {
+
+					case 'Color':
+						maps.map = texture;
+						break;
+
+					case 'Roughness':
+						maps.roughnessMap = texture;
+						maps.roughness = 0.5;
+						break;
+
+					case 'Specular':
+						maps.specularMap = texture;
+						maps.specular = 0xffffff;
+						break;
+
+					case 'Luminous':
+						maps.emissiveMap = texture;
+						maps.emissive = 0x808080;
+						break;
+
+					case 'Luminous THREE.Color':
+						maps.emissive = 0x808080;
+						break;
+
+					case 'Metallic':
+						maps.metalnessMap = texture;
+						maps.metalness = 0.5;
+						break;
+
+					case 'Transparency':
+					case 'Alpha':
+						maps.alphaMap = texture;
+						maps.transparent = true;
+						break;
+
+					case 'Normal':
+						maps.normalMap = texture;
+						if ( node.amplitude !== undefined ) maps.normalScale = new THREE.Vector2( node.amplitude, node.amplitude );
+						break;
+
+					case 'Bump':
+						maps.bumpMap = texture;
+						break;
+
+				}
+
+			} // LWO BSDF materials can have both spec and rough, but this is not valid in three
+
+
+			if ( maps.roughnessMap && maps.specularMap ) delete maps.specularMap;
+			return maps;
+
+		},
+
+		// maps can also be defined on individual material attributes, parse those here
+		// This occurs on Standard (Phong) surfaces
+		parseAttributeImageMaps( attributes, textures, maps ) {
+
+			for ( var name in attributes ) {
+
+				var attribute = attributes[ name ];
+
+				if ( attribute.maps ) {
+
+					var mapData = attribute.maps[ 0 ];
+					var path = this.getTexturePathByIndex( mapData.imageIndex, textures );
+					if ( ! path ) return;
+					var texture = this.loadTexture( path );
+					if ( mapData.wrap !== undefined ) texture.wrapS = this.getWrappingType( mapData.wrap.w );
+					if ( mapData.wrap !== undefined ) texture.wrapT = this.getWrappingType( mapData.wrap.h );
+
+					switch ( name ) {
+
+						case 'Color':
+							maps.map = texture;
+							break;
+
+						case 'Diffuse':
+							maps.aoMap = texture;
+							break;
+
+						case 'Roughness':
+							maps.roughnessMap = texture;
+							maps.roughness = 1;
+							break;
+
+						case 'Specular':
+							maps.specularMap = texture;
+							maps.specular = 0xffffff;
+							break;
+
+						case 'Luminosity':
+							maps.emissiveMap = texture;
+							maps.emissive = 0x808080;
+							break;
+
+						case 'Metallic':
+							maps.metalnessMap = texture;
+							maps.metalness = 1;
+							break;
+
+						case 'Transparency':
+						case 'Alpha':
+							maps.alphaMap = texture;
+							maps.transparent = true;
+							break;
+
+						case 'Normal':
+							maps.normalMap = texture;
+							break;
+
+						case 'Bump':
+							maps.bumpMap = texture;
+							break;
+
+					}
+
+				}
+
+			}
+
+		},
+
+		parseAttributes( attributes, maps ) {
+
+			var params = {}; // don't use color data if color map is present
+
+			if ( attributes.Color && ! maps.map ) {
+
+				params.color = new THREE.Color().fromArray( attributes.Color.value );
+
+			} else params.color = new THREE.Color();
+
+			if ( attributes.Transparency && attributes.Transparency.value !== 0 ) {
+
+				params.opacity = 1 - attributes.Transparency.value;
+				params.transparent = true;
+
+			}
+
+			if ( attributes[ 'Bump Height' ] ) params.bumpScale = attributes[ 'Bump Height' ].value * 0.1;
+			if ( attributes[ 'Refraction Index' ] ) params.refractionRatio = 1 / attributes[ 'Refraction Index' ].value;
+			this.parsePhysicalAttributes( params, attributes, maps );
+			this.parseStandardAttributes( params, attributes, maps );
+			this.parsePhongAttributes( params, attributes, maps );
+			return params;
+
+		},
+
+		parsePhysicalAttributes( params, attributes
+			/*, maps*/
+		) {
+
+			if ( attributes.Clearcoat && attributes.Clearcoat.value > 0 ) {
+
+				params.clearcoat = attributes.Clearcoat.value;
+
+				if ( attributes[ 'Clearcoat Gloss' ] ) {
+
+					params.clearcoatRoughness = 0.5 * ( 1 - attributes[ 'Clearcoat Gloss' ].value );
+
+				}
+
+			}
+
+		},
+
+		parseStandardAttributes( params, attributes, maps ) {
+
+			if ( attributes.Luminous ) {
+
+				params.emissiveIntensity = attributes.Luminous.value;
+
+				if ( attributes[ 'Luminous THREE.Color' ] && ! maps.emissive ) {
+
+					params.emissive = new THREE.Color().fromArray( attributes[ 'Luminous THREE.Color' ].value );
+
+				} else {
+
+					params.emissive = new THREE.Color( 0x808080 );
+
+				}
+
+			}
+
+			if ( attributes.Roughness && ! maps.roughnessMap ) params.roughness = attributes.Roughness.value;
+			if ( attributes.Metallic && ! maps.metalnessMap ) params.metalness = attributes.Metallic.value;
+
+		},
+
+		parsePhongAttributes( params, attributes, maps ) {
+
+			if ( attributes.Diffuse ) params.color.multiplyScalar( attributes.Diffuse.value );
+
+			if ( attributes.Reflection ) {
+
+				params.reflectivity = attributes.Reflection.value;
+				params.combine = THREE.AddOperation;
+
+			}
+
+			if ( attributes.Luminosity ) {
+
+				params.emissiveIntensity = attributes.Luminosity.value;
+
+				if ( ! maps.emissiveMap && ! maps.map ) {
+
+					params.emissive = params.color;
+
+				} else {
+
+					params.emissive = new THREE.Color( 0x808080 );
+
+				}
+
+			} // parse specular if there is no roughness - we will interpret the material as 'Phong' in this case
+
+
+			if ( ! attributes.Roughness && attributes.Specular && ! maps.specularMap ) {
+
+				if ( attributes[ 'Color Highlight' ] ) {
+
+					params.specular = new THREE.Color().setScalar( attributes.Specular.value ).lerp( params.color.clone().multiplyScalar( attributes.Specular.value ), attributes[ 'Color Highlight' ].value );
+
+				} else {
+
+					params.specular = new THREE.Color().setScalar( attributes.Specular.value );
+
+				}
+
+			}
+
+			if ( params.specular && attributes.Glossiness ) params.shininess = 7 + Math.pow( 2, attributes.Glossiness.value * 12 + 2 );
+
+		},
+
+		parseEnvMap( connections, maps, attributes ) {
+
+			if ( connections.envMap ) {
+
+				var envMap = this.loadTexture( connections.envMap );
+
+				if ( attributes.transparent && attributes.opacity < 0.999 ) {
+
+					envMap.mapping = THREE.EquirectangularRefractionMapping; // Reflectivity and refraction mapping don't work well together in Phong materials
+
+					if ( attributes.reflectivity !== undefined ) {
+
+						delete attributes.reflectivity;
+						delete attributes.combine;
+
+					}
+
+					if ( attributes.metalness !== undefined ) {
+
+						delete attributes.metalness;
+
+					}
+
+				} else envMap.mapping = THREE.EquirectangularReflectionMapping;
+
+				maps.envMap = envMap;
+
+			}
+
+		},
+
+		// get texture defined at top level by its index
+		getTexturePathByIndex( index ) {
+
+			var fileName = '';
+			if ( ! lwoTree.textures ) return fileName;
+			lwoTree.textures.forEach( function ( texture ) {
+
+				if ( texture.index === index ) fileName = texture.fileName;
+
+			} );
+			return fileName;
+
+		},
+
+		loadTexture( path ) {
+
+			if ( ! path ) return null;
+			var texture;
+			texture = this.textureLoader.load( path, undefined, undefined, function () {
+
+				console.warn( 'LWOLoader: non-standard resource hierarchy. Use \`resourcePath\` parameter to specify root content directory.' );
+
+			} );
+			return texture;
+
+		},
+
+		// 0 = Reset, 1 = Repeat, 2 = Mirror, 3 = Edge
+		getWrappingType( num ) {
+
+			switch ( num ) {
+
+				case 0:
+					console.warn( 'LWOLoader: "Reset" texture wrapping type is not supported in three.js' );
+					return THREE.ClampToEdgeWrapping;
+
+				case 1:
+					return THREE.RepeatWrapping;
+
+				case 2:
+					return THREE.MirroredRepeatWrapping;
+
+				case 3:
+					return THREE.ClampToEdgeWrapping;
+
+			}
+
+		},
+
+		getMaterialType( nodeData ) {
+
+			if ( nodeData.Clearcoat && nodeData.Clearcoat.value > 0 ) return THREE.MeshPhysicalMaterial;
+			if ( nodeData.Roughness ) return THREE.MeshStandardMaterial;
+			return THREE.MeshPhongMaterial;
+
+		}
+
+	};
+
+	function GeometryParser() {}
+
+	GeometryParser.prototype = {
+		constructor: GeometryParser,
+
+		parse( geoData, layer ) {
+
+			var geometry = new THREE.BufferGeometry();
+			geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( geoData.points, 3 ) );
+			var indices = this.splitIndices( geoData.vertexIndices, geoData.polygonDimensions );
+			geometry.setIndex( indices );
+			this.parseGroups( geometry, geoData );
+			geometry.computeVertexNormals();
+			this.parseUVs( geometry, layer, indices );
+			this.parseMorphTargets( geometry, layer, indices ); // TODO: z may need to be reversed to account for coordinate system change
+
+			geometry.translate( - layer.pivot[ 0 ], - layer.pivot[ 1 ], - layer.pivot[ 2 ] ); // var userData = geometry.userData;
+			// geometry = geometry.toNonIndexed()
+			// geometry.userData = userData;
+
+			return geometry;
+
+		},
+
+		// split quads into tris
+		splitIndices( indices, polygonDimensions ) {
+
+			var remappedIndices = [];
+			var i = 0;
+			polygonDimensions.forEach( function ( dim ) {
+
+				if ( dim < 4 ) {
+
+					for ( var k = 0; k < dim; k ++ ) remappedIndices.push( indices[ i + k ] );
+
+				} else if ( dim === 4 ) {
+
+					remappedIndices.push( indices[ i ], indices[ i + 1 ], indices[ i + 2 ], indices[ i ], indices[ i + 2 ], indices[ i + 3 ] );
+
+				} else if ( dim > 4 ) {
+
+					for ( var k = 1; k < dim - 1; k ++ ) {
+
+						remappedIndices.push( indices[ i ], indices[ i + k ], indices[ i + k + 1 ] );
+
+					}
+
+					console.warn( 'LWOLoader: polygons with greater than 4 sides are not supported' );
+
+				}
+
+				i += dim;
+
+			} );
+			return remappedIndices;
+
+		},
+
+		// NOTE: currently ignoring poly indices and assuming that they are intelligently ordered
+		parseGroups( geometry, geoData ) {
+
+			var tags = lwoTree.tags;
+			var matNames = [];
+			var elemSize = 3;
+			if ( geoData.type === 'lines' ) elemSize = 2;
+			if ( geoData.type === 'points' ) elemSize = 1;
+			var remappedIndices = this.splitMaterialIndices( geoData.polygonDimensions, geoData.materialIndices );
+			var indexNum = 0; // create new indices in numerical order
+
+			var indexPairs = {}; // original indices mapped to numerical indices
+
+			var prevMaterialIndex;
+			var prevStart = 0;
+			var currentCount = 0;
+
+			for ( var i = 0; i < remappedIndices.length; i += 2 ) {
+
+				var materialIndex = remappedIndices[ i + 1 ];
+				if ( i === 0 ) matNames[ indexNum ] = tags[ materialIndex ];
+				if ( prevMaterialIndex === undefined ) prevMaterialIndex = materialIndex;
+
+				if ( materialIndex !== prevMaterialIndex ) {
+
+					var currentIndex;
+
+					if ( indexPairs[ tags[ prevMaterialIndex ] ] ) {
+
+						currentIndex = indexPairs[ tags[ prevMaterialIndex ] ];
+
+					} else {
+
+						currentIndex = indexNum;
+						indexPairs[ tags[ prevMaterialIndex ] ] = indexNum;
+						matNames[ indexNum ] = tags[ prevMaterialIndex ];
+						indexNum ++;
+
+					}
+
+					geometry.addGroup( prevStart, currentCount, currentIndex );
+					prevStart += currentCount;
+					prevMaterialIndex = materialIndex;
+					currentCount = 0;
+
+				}
+
+				currentCount += elemSize;
+
+			} // the loop above doesn't add the last group, do that here.
+
+
+			if ( geometry.groups.length > 0 ) {
+
+				var currentIndex;
+
+				if ( indexPairs[ tags[ materialIndex ] ] ) {
+
+					currentIndex = indexPairs[ tags[ materialIndex ] ];
+
+				} else {
+
+					currentIndex = indexNum;
+					indexPairs[ tags[ materialIndex ] ] = indexNum;
+					matNames[ indexNum ] = tags[ materialIndex ];
+
+				}
+
+				geometry.addGroup( prevStart, currentCount, currentIndex );
+
+			} // Mat names from TAGS chunk, used to build up an array of materials for this geometry
+
+
+			geometry.userData.matNames = matNames;
+
+		},
+
+		splitMaterialIndices( polygonDimensions, indices ) {
+
+			var remappedIndices = [];
+			polygonDimensions.forEach( function ( dim, i ) {
+
+				if ( dim <= 3 ) {
+
+					remappedIndices.push( indices[ i * 2 ], indices[ i * 2 + 1 ] );
+
+				} else if ( dim === 4 ) {
+
+					remappedIndices.push( indices[ i * 2 ], indices[ i * 2 + 1 ], indices[ i * 2 ], indices[ i * 2 + 1 ] );
+
+				} else {
+
+					// ignore > 4 for now
+					for ( var k = 0; k < dim - 2; k ++ ) {
+
+						remappedIndices.push( indices[ i * 2 ], indices[ i * 2 + 1 ] );
+
+					}
+
+				}
+
+			} );
+			return remappedIndices;
+
+		},
+
+		// UV maps:
+		// 1: are defined via index into an array of points, not into a geometry
+		// - the geometry is also defined by an index into this array, but the indexes may not match
+		// 2: there can be any number of UV maps for a single geometry. Here these are combined,
+		// 	with preference given to the first map encountered
+		// 3: UV maps can be partial - that is, defined for only a part of the geometry
+		// 4: UV maps can be VMAP or VMAD (discontinuous, to allow for seams). In practice, most
+		// UV maps are defined as partially VMAP and partially VMAD
+		// VMADs are currently not supported
+		parseUVs( geometry, layer ) {
+
+			// start by creating a UV map set to zero for the whole geometry
+			var remappedUVs = Array.from( Array( geometry.attributes.position.count * 2 ), function () {
+
+				return 0;
+
+			} );
+
+			for ( var name in layer.uvs ) {
+
+				var uvs = layer.uvs[ name ].uvs;
+				var uvIndices = layer.uvs[ name ].uvIndices;
+				uvIndices.forEach( function ( i, j ) {
+
+					remappedUVs[ i * 2 ] = uvs[ j * 2 ];
+					remappedUVs[ i * 2 + 1 ] = uvs[ j * 2 + 1 ];
+
+				} );
+
+			}
+
+			geometry.setAttribute( 'uv', new THREE.Float32BufferAttribute( remappedUVs, 2 ) );
+
+		},
+
+		parseMorphTargets( geometry, layer ) {
+
+			var num = 0;
+
+			for ( var name in layer.morphTargets ) {
+
+				var remappedPoints = geometry.attributes.position.array.slice();
+				if ( ! geometry.morphAttributes.position ) geometry.morphAttributes.position = [];
+				var morphPoints = layer.morphTargets[ name ].points;
+				var morphIndices = layer.morphTargets[ name ].indices;
+				var type = layer.morphTargets[ name ].type;
+				morphIndices.forEach( function ( i, j ) {
+
+					if ( type === 'relative' ) {
+
+						remappedPoints[ i * 3 ] += morphPoints[ j * 3 ];
+						remappedPoints[ i * 3 + 1 ] += morphPoints[ j * 3 + 1 ];
+						remappedPoints[ i * 3 + 2 ] += morphPoints[ j * 3 + 2 ];
+
+					} else {
+
+						remappedPoints[ i * 3 ] = morphPoints[ j * 3 ];
+						remappedPoints[ i * 3 + 1 ] = morphPoints[ j * 3 + 1 ];
+						remappedPoints[ i * 3 + 2 ] = morphPoints[ j * 3 + 2 ];
+
+					}
+
+				} );
+				geometry.morphAttributes.position[ num ] = new THREE.Float32BufferAttribute( remappedPoints, 3 );
+				geometry.morphAttributes.position[ num ].name = name;
+				num ++;
+
+			}
+
+			geometry.morphTargetsRelative = false;
+
+		}
+
+	}; // ************** UTILITY FUNCTIONS **************
+
+	function extractParentUrl( url, dir ) {
+
+		var index = url.indexOf( dir );
+		if ( index === - 1 ) return './';
+		return url.substr( 0, index );
+
+	}
+
+	THREE.LWOLoader = LWOLoader;
+
+} )();

+ 62 - 0
examples/js/loaders/LottieLoader.js

@@ -0,0 +1,62 @@
+( function () {
+
+	class LottieLoader extends THREE.Loader {
+
+		setQuality( value ) {
+
+			this._quality = value;
+
+		}
+
+		load( url, onLoad, onProgress, onError ) {
+
+			const quality = this._quality || 1;
+			const texture = new THREE.CanvasTexture();
+			texture.minFilter = THREE.NearestFilter;
+			const loader = new THREE.FileLoader( this.manager );
+			loader.setPath( this.path );
+			loader.setWithCredentials( this.withCredentials );
+			loader.load( url, function ( text ) {
+
+				const data = JSON.parse( text ); // bodymoving uses container.offetWidth and offsetHeight
+				// to define width/height
+
+				const container = document.createElement( 'div' );
+				container.style.width = data.w + 'px';
+				container.style.height = data.h + 'px';
+				document.body.appendChild( container );
+				const animation = bodymovin.loadAnimation( {
+					container: container,
+					animType: 'canvas',
+					loop: true,
+					autoplay: true,
+					animationData: data,
+					rendererSettings: {
+						dpr: quality
+					}
+				} );
+				texture.animation = animation;
+				texture.image = animation.container;
+				animation.addEventListener( 'enterFrame', function () {
+
+					texture.needsUpdate = true;
+
+				} );
+				container.style.display = 'none';
+
+				if ( onLoad !== undefined ) {
+
+					onLoad( texture );
+
+				}
+
+			}, onProgress, onError );
+			return texture;
+
+		}
+
+	}
+
+	THREE.LottieLoader = LottieLoader;
+
+} )();

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 26 - 170
examples/js/loaders/MD2Loader.js


+ 93 - 0
examples/js/loaders/MDDLoader.js

@@ -0,0 +1,93 @@
+( function () {
+
+	/**
+ * MDD is a special format that stores a position for every vertex in a model for every frame in an animation.
+ * Similar to BVH, it can be used to transfer animation data between different 3D applications or engines.
+ *
+ * MDD stores its data in binary format (big endian) in the following way:
+ *
+ * number of frames (a single uint32)
+ * number of vertices (a single uint32)
+ * time values for each frame (sequence of float32)
+ * vertex data for each frame (sequence of float32)
+ */
+
+	var MDDLoader = function ( manager ) {
+
+		THREE.Loader.call( this, manager );
+
+	};
+
+	MDDLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+		constructor: MDDLoader,
+		load: function ( url, onLoad, onProgress, onError ) {
+
+			var scope = this;
+			var loader = new THREE.FileLoader( this.manager );
+			loader.setPath( this.path );
+			loader.setResponseType( 'arraybuffer' );
+			loader.load( url, function ( data ) {
+
+				onLoad( scope.parse( data ) );
+
+			}, onProgress, onError );
+
+		},
+		parse: function ( data ) {
+
+			var view = new DataView( data );
+			var totalFrames = view.getUint32( 0 );
+			var totalPoints = view.getUint32( 4 );
+			var offset = 8; // animation clip
+
+			var times = new Float32Array( totalFrames );
+			var values = new Float32Array( totalFrames * totalFrames ).fill( 0 );
+
+			for ( var i = 0; i < totalFrames; i ++ ) {
+
+				times[ i ] = view.getFloat32( offset );
+				offset += 4;
+				values[ totalFrames * i + i ] = 1;
+
+			}
+
+			var track = new THREE.NumberKeyframeTrack( '.morphTargetInfluences', times, values );
+			var clip = new THREE.AnimationClip( 'default', times[ times.length - 1 ], [ track ] ); // morph targets
+
+			var morphTargets = [];
+
+			for ( var i = 0; i < totalFrames; i ++ ) {
+
+				var morphTarget = new Float32Array( totalPoints * 3 );
+
+				for ( var j = 0; j < totalPoints; j ++ ) {
+
+					var stride = j * 3;
+					morphTarget[ stride + 0 ] = view.getFloat32( offset );
+					offset += 4; // x
+
+					morphTarget[ stride + 1 ] = view.getFloat32( offset );
+					offset += 4; // y
+
+					morphTarget[ stride + 2 ] = view.getFloat32( offset );
+					offset += 4; // z
+
+				}
+
+				var attribute = new THREE.BufferAttribute( morphTarget, 3 );
+				attribute.name = 'morph_' + i;
+				morphTargets.push( attribute );
+
+			}
+
+			return {
+				morphTargets: morphTargets,
+				clip: clip
+			};
+
+		}
+	} );
+
+	THREE.MDDLoader = MDDLoader;
+
+} )();

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 115 - 171
examples/js/loaders/MMDLoader.js


+ 282 - 351
examples/js/loaders/MTLLoader.js

@@ -1,18 +1,19 @@
-/**
+( function () {
+
+	/**
  * Loads a Wavefront .mtl file specifying materials
  */
 
-THREE.MTLLoader = function ( manager ) {
-
-	THREE.Loader.call( this, manager );
+	var MTLLoader = function ( manager ) {
 
-};
+		THREE.Loader.call( this, manager );
 
-THREE.MTLLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+	};
 
-	constructor: THREE.MTLLoader,
+	MTLLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+		constructor: MTLLoader,
 
-	/**
+		/**
 	 * Loads and parses a MTL asset from a URL.
 	 *
 	 * @param {String} url - URL to the MTL file.
@@ -25,525 +26,455 @@ THREE.MTLLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype
 	 * @note In order for relative texture references to resolve correctly
 	 * you must call setResourcePath() explicitly prior to load.
 	 */
-	load: function ( url, onLoad, onProgress, onError ) {
+		load: function ( url, onLoad, onProgress, onError ) {
 
-		var scope = this;
+			var scope = this;
+			var path = this.path === '' ? THREE.LoaderUtils.extractUrlBase( url ) : this.path;
+			var loader = new THREE.FileLoader( this.manager );
+			loader.setPath( this.path );
+			loader.setRequestHeader( this.requestHeader );
+			loader.setWithCredentials( this.withCredentials );
+			loader.load( url, function ( text ) {
 
-		var path = ( this.path === '' ) ? THREE.LoaderUtils.extractUrlBase( url ) : this.path;
+				try {
 
-		var loader = new THREE.FileLoader( this.manager );
-		loader.setPath( this.path );
-		loader.setRequestHeader( this.requestHeader );
-		loader.setWithCredentials( this.withCredentials );
-		loader.load( url, function ( text ) {
+					onLoad( scope.parse( text, path ) );
 
-			try {
+				} catch ( e ) {
 
-				onLoad( scope.parse( text, path ) );
+					if ( onError ) {
 
-			} catch ( e ) {
+						onError( e );
 
-				if ( onError ) {
+					} else {
 
-					onError( e );
+						console.error( e );
 
-				} else {
+					}
 
-					console.error( e );
+					scope.manager.itemError( url );
 
 				}
 
-				scope.manager.itemError( url );
-
-			}
-
-		}, onProgress, onError );
-
-	},
+			}, onProgress, onError );
 
-	setMaterialOptions: function ( value ) {
+		},
+		setMaterialOptions: function ( value ) {
 
-		this.materialOptions = value;
-		return this;
+			this.materialOptions = value;
+			return this;
 
-	},
+		},
 
-	/**
+		/**
 	 * Parses a MTL file.
 	 *
 	 * @param {String} text - Content of MTL file
-	 * @return {THREE.MTLLoader.MaterialCreator}
+	 * @return {MTLLoader.MaterialCreator}
 	 *
 	 * @see setPath setResourcePath
 	 *
 	 * @note In order for relative texture references to resolve correctly
 	 * you must call setResourcePath() explicitly prior to parse.
 	 */
-	parse: function ( text, path ) {
-
-		var lines = text.split( '\n' );
-		var info = {};
-		var delimiter_pattern = /\s+/;
-		var materialsInfo = {};
+		parse: function ( text, path ) {
 
-		for ( var i = 0; i < lines.length; i ++ ) {
+			var lines = text.split( '\n' );
+			var info = {};
+			var delimiter_pattern = /\s+/;
+			var materialsInfo = {};
 
-			var line = lines[ i ];
-			line = line.trim();
+			for ( var i = 0; i < lines.length; i ++ ) {
 
-			if ( line.length === 0 || line.charAt( 0 ) === '#' ) {
+				var line = lines[ i ];
+				line = line.trim();
 
-				// Blank line or comment ignore
-				continue;
+				if ( line.length === 0 || line.charAt( 0 ) === '#' ) {
 
-			}
+					// Blank line or comment ignore
+					continue;
 
-			var pos = line.indexOf( ' ' );
-
-			var key = ( pos >= 0 ) ? line.substring( 0, pos ) : line;
-			key = key.toLowerCase();
+				}
 
-			var value = ( pos >= 0 ) ? line.substring( pos + 1 ) : '';
-			value = value.trim();
+				var pos = line.indexOf( ' ' );
+				var key = pos >= 0 ? line.substring( 0, pos ) : line;
+				key = key.toLowerCase();
+				var value = pos >= 0 ? line.substring( pos + 1 ) : '';
+				value = value.trim();
 
-			if ( key === 'newmtl' ) {
+				if ( key === 'newmtl' ) {
 
-				// New material
+					// New material
+					info = {
+						name: value
+					};
+					materialsInfo[ value ] = info;
 
-				info = { name: value };
-				materialsInfo[ value ] = info;
+				} else {
 
-			} else {
+					if ( key === 'ka' || key === 'kd' || key === 'ks' || key === 'ke' ) {
 
-				if ( key === 'ka' || key === 'kd' || key === 'ks' || key === 'ke' ) {
+						var ss = value.split( delimiter_pattern, 3 );
+						info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ];
 
-					var ss = value.split( delimiter_pattern, 3 );
-					info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ];
+					} else {
 
-				} else {
+						info[ key ] = value;
 
-					info[ key ] = value;
+					}
 
 				}
 
 			}
 
-		}
+			var materialCreator = new MTLLoader.MaterialCreator( this.resourcePath || path, this.materialOptions );
+			materialCreator.setCrossOrigin( this.crossOrigin );
+			materialCreator.setManager( this.manager );
+			materialCreator.setMaterials( materialsInfo );
+			return materialCreator;
 
-		var materialCreator = new THREE.MTLLoader.MaterialCreator( this.resourcePath || path, this.materialOptions );
-		materialCreator.setCrossOrigin( this.crossOrigin );
-		materialCreator.setManager( this.manager );
-		materialCreator.setMaterials( materialsInfo );
-		return materialCreator;
-
-	}
-
-} );
-
-/**
- * Create a new THREE.MTLLoader.MaterialCreator
+		}
+	} );
+	/**
+ * Create a new MTLLoader.MaterialCreator
  * @param baseUrl - Url relative to which textures are loaded
  * @param options - Set of options on how to construct the materials
- *                  side: Which side to apply the material
- *                        THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide
- *                  wrap: What type of wrapping to apply for textures
- *                        THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping
- *                  normalizeRGB: RGBs need to be normalized to 0-1 from 0-255
- *                                Default: false, assumed to be already normalized
- *                  ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's
- *                                  Default: false
+ *									side: Which side to apply the material
+ *												THREE.FrontSide (default), THREE.BackSide, THREE.DoubleSide
+ *									wrap: What type of wrapping to apply for textures
+ *												THREE.RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping
+ *									normalizeRGB: RGBs need to be normalized to 0-1 from 0-255
+ *																Default: false, assumed to be already normalized
+ *									ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's
+ *																	Default: false
  * @constructor
  */
 
-THREE.MTLLoader.MaterialCreator = function ( baseUrl, options ) {
-
-	this.baseUrl = baseUrl || '';
-	this.options = options;
-	this.materialsInfo = {};
-	this.materials = {};
-	this.materialsArray = [];
-	this.nameLookup = {};
-
-	this.side = ( this.options && this.options.side ) ? this.options.side : THREE.FrontSide;
-	this.wrap = ( this.options && this.options.wrap ) ? this.options.wrap : THREE.RepeatWrapping;
-
-};
-
-THREE.MTLLoader.MaterialCreator.prototype = {
-
-	constructor: THREE.MTLLoader.MaterialCreator,
-
-	crossOrigin: 'anonymous',
-
-	setCrossOrigin: function ( value ) {
-
-		this.crossOrigin = value;
-		return this;
-
-	},
-
-	setManager: function ( value ) {
-
-		this.manager = value;
-
-	},
-
-	setMaterials: function ( materialsInfo ) {
+	MTLLoader.MaterialCreator = function ( baseUrl, options ) {
 
-		this.materialsInfo = this.convert( materialsInfo );
+		this.baseUrl = baseUrl || '';
+		this.options = options;
+		this.materialsInfo = {};
 		this.materials = {};
 		this.materialsArray = [];
 		this.nameLookup = {};
+		this.side = this.options && this.options.side ? this.options.side : THREE.FrontSide;
+		this.wrap = this.options && this.options.wrap ? this.options.wrap : THREE.RepeatWrapping;
 
-	},
+	};
 
-	convert: function ( materialsInfo ) {
+	MTLLoader.MaterialCreator.prototype = {
+		constructor: MTLLoader.MaterialCreator,
+		crossOrigin: 'anonymous',
+		setCrossOrigin: function ( value ) {
 
-		if ( ! this.options ) return materialsInfo;
+			this.crossOrigin = value;
+			return this;
 
-		var converted = {};
+		},
+		setManager: function ( value ) {
 
-		for ( var mn in materialsInfo ) {
+			this.manager = value;
 
-			// Convert materials info into normalized form based on options
+		},
+		setMaterials: function ( materialsInfo ) {
 
-			var mat = materialsInfo[ mn ];
+			this.materialsInfo = this.convert( materialsInfo );
+			this.materials = {};
+			this.materialsArray = [];
+			this.nameLookup = {};
 
-			var covmat = {};
+		},
+		convert: function ( materialsInfo ) {
 
-			converted[ mn ] = covmat;
+			if ( ! this.options ) return materialsInfo;
+			var converted = {};
 
-			for ( var prop in mat ) {
+			for ( var mn in materialsInfo ) {
 
-				var save = true;
-				var value = mat[ prop ];
-				var lprop = prop.toLowerCase();
+				// Convert materials info into normalized form based on options
+				var mat = materialsInfo[ mn ];
+				var covmat = {};
+				converted[ mn ] = covmat;
 
-				switch ( lprop ) {
+				for ( var prop in mat ) {
 
-					case 'kd':
-					case 'ka':
-					case 'ks':
+					var save = true;
+					var value = mat[ prop ];
+					var lprop = prop.toLowerCase();
 
-						// Diffuse color (color under white light) using RGB values
+					switch ( lprop ) {
 
-						if ( this.options && this.options.normalizeRGB ) {
+						case 'kd':
+						case 'ka':
+						case 'ks':
+						// Diffuse color (color under white light) using RGB values
+							if ( this.options && this.options.normalizeRGB ) {
 
-							value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ];
+								value = [ value[ 0 ] / 255, value[ 1 ] / 255, value[ 2 ] / 255 ];
 
-						}
+							}
 
-						if ( this.options && this.options.ignoreZeroRGBs ) {
+							if ( this.options && this.options.ignoreZeroRGBs ) {
 
-							if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 2 ] === 0 ) {
+								if ( value[ 0 ] === 0 && value[ 1 ] === 0 && value[ 2 ] === 0 ) {
 
-								// ignore
+									// ignore
+									save = false;
 
-								save = false;
+								}
 
 							}
 
-						}
+							break;
 
-						break;
+						default:
+							break;
 
-					default:
-
-						break;
+					}
 
-				}
+					if ( save ) {
 
-				if ( save ) {
+						covmat[ lprop ] = value;
 
-					covmat[ lprop ] = value;
+					}
 
 				}
 
 			}
 
-		}
-
-		return converted;
-
-	},
+			return converted;
 
-	preload: function () {
+		},
+		preload: function () {
 
-		for ( var mn in this.materialsInfo ) {
+			for ( var mn in this.materialsInfo ) {
 
-			this.create( mn );
+				this.create( mn );
 
-		}
-
-	},
-
-	getIndex: function ( materialName ) {
-
-		return this.nameLookup[ materialName ];
-
-	},
-
-	getAsArray: function () {
-
-		var index = 0;
-
-		for ( var mn in this.materialsInfo ) {
-
-			this.materialsArray[ index ] = this.create( mn );
-			this.nameLookup[ mn ] = index;
-			index ++;
-
-		}
-
-		return this.materialsArray;
-
-	},
-
-	create: function ( materialName ) {
-
-		if ( this.materials[ materialName ] === undefined ) {
-
-			this.createMaterial_( materialName );
+			}
 
-		}
+		},
+		getIndex: function ( materialName ) {
 
-		return this.materials[ materialName ];
+			return this.nameLookup[ materialName ];
 
-	},
+		},
+		getAsArray: function () {
 
-	createMaterial_: function ( materialName ) {
+			var index = 0;
 
-		// Create material
+			for ( var mn in this.materialsInfo ) {
 
-		var scope = this;
-		var mat = this.materialsInfo[ materialName ];
-		var params = {
+				this.materialsArray[ index ] = this.create( mn );
+				this.nameLookup[ mn ] = index;
+				index ++;
 
-			name: materialName,
-			side: this.side
+			}
 
-		};
+			return this.materialsArray;
 
-		function resolveURL( baseUrl, url ) {
+		},
+		create: function ( materialName ) {
 
-			if ( typeof url !== 'string' || url === '' )
-				return '';
+			if ( this.materials[ materialName ] === undefined ) {
 
-			// Absolute URL
-			if ( /^https?:\/\//i.test( url ) ) return url;
+				this.createMaterial_( materialName );
 
-			return baseUrl + url;
+			}
 
-		}
+			return this.materials[ materialName ];
 
-		function setMapForType( mapType, value ) {
+		},
+		createMaterial_: function ( materialName ) {
 
-			if ( params[ mapType ] ) return; // Keep the first encountered texture
+			// Create material
+			var scope = this;
+			var mat = this.materialsInfo[ materialName ];
+			var params = {
+				name: materialName,
+				side: this.side
+			};
 
-			var texParams = scope.getTextureParams( value, params );
-			var map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) );
+			function resolveURL( baseUrl, url ) {
 
-			map.repeat.copy( texParams.scale );
-			map.offset.copy( texParams.offset );
+				if ( typeof url !== 'string' || url === '' ) return ''; // Absolute URL
 
-			map.wrapS = scope.wrap;
-			map.wrapT = scope.wrap;
+				if ( /^https?:\/\//i.test( url ) ) return url;
+				return baseUrl + url;
 
-			params[ mapType ] = map;
+			}
 
-		}
+			function setMapForType( mapType, value ) {
 
-		for ( var prop in mat ) {
+				if ( params[ mapType ] ) return; // Keep the first encountered texture
 
-			var value = mat[ prop ];
-			var n;
+				var texParams = scope.getTextureParams( value, params );
+				var map = scope.loadTexture( resolveURL( scope.baseUrl, texParams.url ) );
+				map.repeat.copy( texParams.scale );
+				map.offset.copy( texParams.offset );
+				map.wrapS = scope.wrap;
+				map.wrapT = scope.wrap;
+				params[ mapType ] = map;
 
-			if ( value === '' ) continue;
+			}
 
-			switch ( prop.toLowerCase() ) {
+			for ( var prop in mat ) {
 
-				// Ns is material specular exponent
+				var value = mat[ prop ];
+				var n;
+				if ( value === '' ) continue;
 
-				case 'kd':
+				switch ( prop.toLowerCase() ) {
 
+					// Ns is material specular exponent
+					case 'kd':
 					// Diffuse color (color under white light) using RGB values
+						params.color = new THREE.Color().fromArray( value );
+						break;
 
-					params.color = new THREE.Color().fromArray( value );
-
-					break;
-
-				case 'ks':
-
+					case 'ks':
 					// Specular color (color when light is reflected from shiny surface) using RGB values
-					params.specular = new THREE.Color().fromArray( value );
-
-					break;
-
-				case 'ke':
+						params.specular = new THREE.Color().fromArray( value );
+						break;
 
+					case 'ke':
 					// Emissive using RGB values
-					params.emissive = new THREE.Color().fromArray( value );
-
-					break;
-
-				case 'map_kd':
+						params.emissive = new THREE.Color().fromArray( value );
+						break;
 
+					case 'map_kd':
 					// Diffuse texture map
+						setMapForType( 'map', value );
+						break;
 
-					setMapForType( 'map', value );
-
-					break;
-
-				case 'map_ks':
-
+					case 'map_ks':
 					// Specular map
+						setMapForType( 'specularMap', value );
+						break;
 
-					setMapForType( 'specularMap', value );
-
-					break;
-
-				case 'map_ke':
-
+					case 'map_ke':
 					// Emissive map
+						setMapForType( 'emissiveMap', value );
+						break;
 
-					setMapForType( 'emissiveMap', value );
-
-					break;
-
-				case 'norm':
-
-					setMapForType( 'normalMap', value );
-
-					break;
-
-				case 'map_bump':
-				case 'bump':
+					case 'norm':
+						setMapForType( 'normalMap', value );
+						break;
 
+					case 'map_bump':
+					case 'bump':
 					// Bump texture map
+						setMapForType( 'bumpMap', value );
+						break;
 
-					setMapForType( 'bumpMap', value );
-
-					break;
-
-				case 'map_d':
-
+					case 'map_d':
 					// Alpha map
+						setMapForType( 'alphaMap', value );
+						params.transparent = true;
+						break;
 
-					setMapForType( 'alphaMap', value );
-					params.transparent = true;
-
-					break;
-
-				case 'ns':
-
+					case 'ns':
 					// The specular exponent (defines the focus of the specular highlight)
 					// A high exponent results in a tight, concentrated highlight. Ns values normally range from 0 to 1000.
+						params.shininess = parseFloat( value );
+						break;
 
-					params.shininess = parseFloat( value );
-
-					break;
+					case 'd':
+						n = parseFloat( value );
 
-				case 'd':
-					n = parseFloat( value );
+						if ( n < 1 ) {
 
-					if ( n < 1 ) {
+							params.opacity = n;
+							params.transparent = true;
 
-						params.opacity = n;
-						params.transparent = true;
-
-					}
+						}
 
-					break;
+						break;
 
-				case 'tr':
-					n = parseFloat( value );
+					case 'tr':
+						n = parseFloat( value );
+						if ( this.options && this.options.invertTrProperty ) n = 1 - n;
 
-					if ( this.options && this.options.invertTrProperty ) n = 1 - n;
+						if ( n > 0 ) {
 
-					if ( n > 0 ) {
+							params.opacity = 1 - n;
+							params.transparent = true;
 
-						params.opacity = 1 - n;
-						params.transparent = true;
+						}
 
-					}
+						break;
 
-					break;
+					default:
+						break;
 
-				default:
-					break;
+				}
 
 			}
 
-		}
-
-		this.materials[ materialName ] = new THREE.MeshPhongMaterial( params );
-		return this.materials[ materialName ];
-
-	},
+			this.materials[ materialName ] = new THREE.MeshPhongMaterial( params );
+			return this.materials[ materialName ];
 
-	getTextureParams: function ( value, matParams ) {
+		},
+		getTextureParams: function ( value, matParams ) {
 
-		var texParams = {
+			var texParams = {
+				scale: new THREE.Vector2( 1, 1 ),
+				offset: new THREE.Vector2( 0, 0 )
+			};
+			var items = value.split( /\s+/ );
+			var pos;
+			pos = items.indexOf( '-bm' );
 
-			scale: new THREE.Vector2( 1, 1 ),
-			offset: new THREE.Vector2( 0, 0 )
+			if ( pos >= 0 ) {
 
-		 };
+				matParams.bumpScale = parseFloat( items[ pos + 1 ] );
+				items.splice( pos, 2 );
 
-		var items = value.split( /\s+/ );
-		var pos;
-
-		pos = items.indexOf( '-bm' );
-
-		if ( pos >= 0 ) {
-
-			matParams.bumpScale = parseFloat( items[ pos + 1 ] );
-			items.splice( pos, 2 );
+			}
 
-		}
+			pos = items.indexOf( '-s' );
 
-		pos = items.indexOf( '-s' );
+			if ( pos >= 0 ) {
 
-		if ( pos >= 0 ) {
+				texParams.scale.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
+				items.splice( pos, 4 ); // we expect 3 parameters here!
 
-			texParams.scale.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
-			items.splice( pos, 4 ); // we expect 3 parameters here!
+			}
 
-		}
+			pos = items.indexOf( '-o' );
 
-		pos = items.indexOf( '-o' );
+			if ( pos >= 0 ) {
 
-		if ( pos >= 0 ) {
+				texParams.offset.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
+				items.splice( pos, 4 ); // we expect 3 parameters here!
 
-			texParams.offset.set( parseFloat( items[ pos + 1 ] ), parseFloat( items[ pos + 2 ] ) );
-			items.splice( pos, 4 ); // we expect 3 parameters here!
+			}
 
-		}
+			texParams.url = items.join( ' ' ).trim();
+			return texParams;
 
-		texParams.url = items.join( ' ' ).trim();
-		return texParams;
+		},
+		loadTexture: function ( url, mapping, onLoad, onProgress, onError ) {
 
-	},
+			var texture;
+			var manager = this.manager !== undefined ? this.manager : THREE.DefaultLoadingManager;
+			var loader = manager.getHandler( url );
 
-	loadTexture: function ( url, mapping, onLoad, onProgress, onError ) {
+			if ( loader === null ) {
 
-		var texture;
-		var manager = ( this.manager !== undefined ) ? this.manager : THREE.DefaultLoadingManager;
-		var loader = manager.getHandler( url );
+				loader = new THREE.TextureLoader( manager );
 
-		if ( loader === null ) {
+			}
 
-			loader = new THREE.TextureLoader( manager );
+			if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin );
+			texture = loader.load( url, onLoad, onProgress, onError );
+			if ( mapping !== undefined ) texture.mapping = mapping;
+			return texture;
 
 		}
+	};
 
-		if ( loader.setCrossOrigin ) loader.setCrossOrigin( this.crossOrigin );
-		texture = loader.load( url, onLoad, onProgress, onError );
-
-		if ( mapping !== undefined ) texture.mapping = mapping;
-
-		return texture;
-
-	}
+	THREE.MTLLoader = MTLLoader;
 
-};
+} )();

+ 408 - 407
examples/js/loaders/NRRDLoader.js

@@ -1,215 +1,222 @@
-THREE.NRRDLoader = function ( manager ) {
+( function () {
 
-	THREE.Loader.call( this, manager );
+	var NRRDLoader = function ( manager ) {
 
-};
+		THREE.Loader.call( this, manager );
 
-THREE.NRRDLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+	};
 
-	constructor: THREE.NRRDLoader,
+	NRRDLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+		constructor: NRRDLoader,
+		load: function ( url, onLoad, onProgress, onError ) {
 
-	load: function ( url, onLoad, onProgress, onError ) {
+			var scope = this;
+			var loader = new THREE.FileLoader( scope.manager );
+			loader.setPath( scope.path );
+			loader.setResponseType( 'arraybuffer' );
+			loader.setRequestHeader( scope.requestHeader );
+			loader.setWithCredentials( scope.withCredentials );
+			loader.load( url, function ( data ) {
 
-		var scope = this;
+				try {
 
-		var loader = new THREE.FileLoader( scope.manager );
-		loader.setPath( scope.path );
-		loader.setResponseType( 'arraybuffer' );
-		loader.setRequestHeader( scope.requestHeader );
-		loader.setWithCredentials( scope.withCredentials );
-		loader.load( url, function ( data ) {
+					onLoad( scope.parse( data ) );
 
-			try {
+				} catch ( e ) {
 
-				onLoad( scope.parse( data ) );
+					if ( onError ) {
 
-			} catch ( e ) {
+						onError( e );
 
-				if ( onError ) {
+					} else {
 
-					onError( e );
+						console.error( e );
 
-				} else {
+					}
 
-					console.error( e );
+					scope.manager.itemError( url );
 
 				}
 
-				scope.manager.itemError( url );
+			}, onProgress, onError );
 
-			}
+		},
+		parse: function ( data ) {
 
-		}, onProgress, onError );
+			// this parser is largely inspired from the XTK NRRD parser : https://github.com/xtk/X
+			var _data = data;
+			var _dataPointer = 0;
 
-	},
+			var _nativeLittleEndian = new Int8Array( new Int16Array( [ 1 ] ).buffer )[ 0 ] > 0;
 
-	parse: function ( data ) {
+			var _littleEndian = true;
+			var headerObject = {};
 
-		// this parser is largely inspired from the XTK NRRD parser : https://github.com/xtk/X
+			function scan( type, chunks ) {
 
-		var _data = data;
+				if ( chunks === undefined || chunks === null ) {
 
-		var _dataPointer = 0;
+					chunks = 1;
 
-		var _nativeLittleEndian = new Int8Array( new Int16Array( [ 1 ] ).buffer )[ 0 ] > 0;
+				}
 
-		var _littleEndian = true;
+				var _chunkSize = 1;
+				var _array_type = Uint8Array;
 
-		var headerObject = {};
+				switch ( type ) {
 
-		function scan( type, chunks ) {
+					// 1 byte data types
+					case 'uchar':
+						break;
 
-			if ( chunks === undefined || chunks === null ) {
+					case 'schar':
+						_array_type = Int8Array;
+						break;
+						// 2 byte data types
 
-				chunks = 1;
+					case 'ushort':
+						_array_type = Uint16Array;
+						_chunkSize = 2;
+						break;
 
-			}
+					case 'sshort':
+						_array_type = Int16Array;
+						_chunkSize = 2;
+						break;
+						// 4 byte data types
 
-			var _chunkSize = 1;
-			var _array_type = Uint8Array;
+					case 'uint':
+						_array_type = Uint32Array;
+						_chunkSize = 4;
+						break;
 
-			switch ( type ) {
+					case 'sint':
+						_array_type = Int32Array;
+						_chunkSize = 4;
+						break;
 
-				// 1 byte data types
-				case 'uchar':
-					break;
-				case 'schar':
-					_array_type = Int8Array;
-					break;
-					// 2 byte data types
-				case 'ushort':
-					_array_type = Uint16Array;
-					_chunkSize = 2;
-					break;
-				case 'sshort':
-					_array_type = Int16Array;
-					_chunkSize = 2;
-					break;
-					// 4 byte data types
-				case 'uint':
-					_array_type = Uint32Array;
-					_chunkSize = 4;
-					break;
-				case 'sint':
-					_array_type = Int32Array;
-					_chunkSize = 4;
-					break;
-				case 'float':
-					_array_type = Float32Array;
-					_chunkSize = 4;
-					break;
-				case 'complex':
-					_array_type = Float64Array;
-					_chunkSize = 8;
-					break;
-				case 'double':
-					_array_type = Float64Array;
-					_chunkSize = 8;
-					break;
+					case 'float':
+						_array_type = Float32Array;
+						_chunkSize = 4;
+						break;
 
-			}
+					case 'complex':
+						_array_type = Float64Array;
+						_chunkSize = 8;
+						break;
 
-			// increase the data pointer in-place
-			var _bytes = new _array_type( _data.slice( _dataPointer,
-				_dataPointer += chunks * _chunkSize ) );
+					case 'double':
+						_array_type = Float64Array;
+						_chunkSize = 8;
+						break;
 
-			// if required, flip the endianness of the bytes
-			if ( _nativeLittleEndian != _littleEndian ) {
+				} // increase the data pointer in-place
 
-				// we need to flip here since the format doesn't match the native endianness
-				_bytes = flipEndianness( _bytes, _chunkSize );
 
-			}
+				var _bytes = new _array_type( _data.slice( _dataPointer, _dataPointer += chunks * _chunkSize ) ); // if required, flip the endianness of the bytes
 
-			if ( chunks == 1 ) {
 
-				// if only one chunk was requested, just return one value
-				return _bytes[ 0 ];
+				if ( _nativeLittleEndian != _littleEndian ) {
 
-			}
+					// we need to flip here since the format doesn't match the native endianness
+					_bytes = flipEndianness( _bytes, _chunkSize );
+
+				}
 
-			// return the byte array
-			return _bytes;
+				if ( chunks == 1 ) {
+
+					// if only one chunk was requested, just return one value
+					return _bytes[ 0 ];
+
+				} // return the byte array
 
-		}
 
-		//Flips typed array endianness in-place. Based on https://github.com/kig/DataStream.js/blob/master/DataStream.js.
+				return _bytes;
 
-		function flipEndianness( array, chunkSize ) {
+			} //Flips typed array endianness in-place. Based on https://github.com/kig/DataStream.js/blob/master/DataStream.js.
 
-			var u8 = new Uint8Array( array.buffer, array.byteOffset, array.byteLength );
-			for ( var i = 0; i < array.byteLength; i += chunkSize ) {
 
-				for ( var j = i + chunkSize - 1, k = i; j > k; j --, k ++ ) {
+			function flipEndianness( array, chunkSize ) {
 
-					var tmp = u8[ k ];
-					u8[ k ] = u8[ j ];
-					u8[ j ] = tmp;
+				var u8 = new Uint8Array( array.buffer, array.byteOffset, array.byteLength );
+
+				for ( var i = 0; i < array.byteLength; i += chunkSize ) {
+
+					for ( var j = i + chunkSize - 1, k = i; j > k; j --, k ++ ) {
+
+						var tmp = u8[ k ];
+						u8[ k ] = u8[ j ];
+						u8[ j ] = tmp;
+
+					}
 
 				}
 
-			}
+				return array;
 
-			return array;
+			} //parse the header
 
-		}
 
-		//parse the header
-		function parseHeader( header ) {
+			function parseHeader( header ) {
 
-			var data, field, fn, i, l, lines, m, _i, _len;
-			lines = header.split( /\r?\n/ );
-			for ( _i = 0, _len = lines.length; _i < _len; _i ++ ) {
+				var data, field, fn, i, l, lines, m, _i, _len;
 
-				l = lines[ _i ];
-				if ( l.match( /NRRD\d+/ ) ) {
+				lines = header.split( /\r?\n/ );
 
-					headerObject.isNrrd = true;
+				for ( _i = 0, _len = lines.length; _i < _len; _i ++ ) {
 
-				} else if ( l.match( /^#/ ) ) {
-				} else if ( m = l.match( /(.*):(.*)/ ) ) {
+					l = lines[ _i ];
 
-					field = m[ 1 ].trim();
-					data = m[ 2 ].trim();
-					fn = THREE.NRRDLoader.prototype.fieldFunctions[ field ];
-					if ( fn ) {
+					if ( l.match( /NRRD\d+/ ) ) {
 
-						fn.call( headerObject, data );
+						headerObject.isNrrd = true;
 
-					} else {
+					} else if ( l.match( /^#/ ) ) {} else if ( m = l.match( /(.*):(.*)/ ) ) {
+
+						field = m[ 1 ].trim();
+						data = m[ 2 ].trim();
+						fn = NRRDLoader.prototype.fieldFunctions[ field ];
+
+						if ( fn ) {
+
+							fn.call( headerObject, data );
 
-						headerObject[ field ] = data;
+						} else {
+
+							headerObject[ field ] = data;
+
+						}
 
 					}
 
 				}
 
-			}
+				if ( ! headerObject.isNrrd ) {
 
-			if ( ! headerObject.isNrrd ) {
+					throw new Error( 'Not an NRRD file' );
 
-				throw new Error( 'Not an NRRD file' );
+				}
 
-			}
+				if ( headerObject.encoding === 'bz2' || headerObject.encoding === 'bzip2' ) {
 
-			if ( headerObject.encoding === 'bz2' || headerObject.encoding === 'bzip2' ) {
+					throw new Error( 'Bzip is not supported' );
 
-				throw new Error( 'Bzip is not supported' );
+				}
 
-			}
+				if ( ! headerObject.vectors ) {
 
-			if ( ! headerObject.vectors ) {
+					//if no space direction is set, let's use the identity
+					headerObject.vectors = [ new THREE.Vector3( 1, 0, 0 ), new THREE.Vector3( 0, 1, 0 ), new THREE.Vector3( 0, 0, 1 ) ]; //apply spacing if defined
+
+					if ( headerObject.spacings ) {
 
-				//if no space direction is set, let's use the identity
-				headerObject.vectors = [ new THREE.Vector3( 1, 0, 0 ), new THREE.Vector3( 0, 1, 0 ), new THREE.Vector3( 0, 0, 1 ) ];
-				//apply spacing if defined
-				if ( headerObject.spacings ) {
+						for ( i = 0; i <= 2; i ++ ) {
 
-					for ( i = 0; i <= 2; i ++ ) {
+							if ( ! isNaN( headerObject.spacings[ i ] ) ) {
 
-						if ( ! isNaN( headerObject.spacings[ i ] ) ) {
+								headerObject.vectors[ i ].multiplyScalar( headerObject.spacings[ i ] );
 
-							headerObject.vectors[ i ].multiplyScalar( headerObject.spacings[ i ] );
+							}
 
 						}
 
@@ -217,412 +224,406 @@ THREE.NRRDLoader.prototype = Object.assign( Object.create( THREE.Loader.prototyp
 
 				}
 
-			}
+			} //parse the data when registred as one of this type : 'text', 'ascii', 'txt'
 
-		}
 
-		//parse the data when registred as one of this type : 'text', 'ascii', 'txt'
-		function parseDataAsText( data, start, end ) {
+			function parseDataAsText( data, start, end ) {
 
-			var number = '';
-			start = start || 0;
-			end = end || data.length;
-			var value;
-			//length of the result is the product of the sizes
-			var lengthOfTheResult = headerObject.sizes.reduce( function ( previous, current ) {
+				var number = '';
+				start = start || 0;
+				end = end || data.length;
+				var value; //length of the result is the product of the sizes
 
-				return previous * current;
+				var lengthOfTheResult = headerObject.sizes.reduce( function ( previous, current ) {
 
-			}, 1 );
+					return previous * current;
 
-			var base = 10;
-			if ( headerObject.encoding === 'hex' ) {
+				}, 1 );
+				var base = 10;
 
-				base = 16;
+				if ( headerObject.encoding === 'hex' ) {
 
-			}
+					base = 16;
 
-			var result = new headerObject.__array( lengthOfTheResult );
-			var resultIndex = 0;
-			var parsingFunction = parseInt;
-			if ( headerObject.__array === Float32Array || headerObject.__array === Float64Array ) {
+				}
 
-				parsingFunction = parseFloat;
+				var result = new headerObject.__array( lengthOfTheResult );
+				var resultIndex = 0;
+				var parsingFunction = parseInt;
 
-			}
+				if ( headerObject.__array === Float32Array || headerObject.__array === Float64Array ) {
+
+					parsingFunction = parseFloat;
+
+				}
 
-			for ( var i = start; i < end; i ++ ) {
+				for ( var i = start; i < end; i ++ ) {
 
-				value = data[ i ];
-				//if value is not a space
-				if ( ( value < 9 || value > 13 ) && value !== 32 ) {
+					value = data[ i ]; //if value is not a space
 
-					number += String.fromCharCode( value );
+					if ( ( value < 9 || value > 13 ) && value !== 32 ) {
 
-				} else {
+						number += String.fromCharCode( value );
 
-					if ( number !== '' ) {
+					} else {
+
+						if ( number !== '' ) {
 
-						result[ resultIndex ] = parsingFunction( number, base );
-						resultIndex ++;
+							result[ resultIndex ] = parsingFunction( number, base );
+							resultIndex ++;
+
+						}
+
+						number = '';
 
 					}
 
-					number = '';
+				}
+
+				if ( number !== '' ) {
+
+					result[ resultIndex ] = parsingFunction( number, base );
+					resultIndex ++;
 
 				}
 
+				return result;
+
 			}
 
-			if ( number !== '' ) {
+			var _bytes = scan( 'uchar', data.byteLength );
 
-				result[ resultIndex ] = parsingFunction( number, base );
-				resultIndex ++;
+			var _length = _bytes.length;
+			var _header = null;
+			var _data_start = 0;
+			var i;
 
-			}
+			for ( i = 1; i < _length; i ++ ) {
 
-			return result;
+				if ( _bytes[ i - 1 ] == 10 && _bytes[ i ] == 10 ) {
 
-		}
+					// we found two line breaks in a row
+					// now we know what the header is
+					_header = this.parseChars( _bytes, 0, i - 2 ); // this is were the data starts
 
-		var _bytes = scan( 'uchar', data.byteLength );
-		var _length = _bytes.length;
-		var _header = null;
-		var _data_start = 0;
-		var i;
-		for ( i = 1; i < _length; i ++ ) {
+					_data_start = i + 1;
+					break;
 
-			if ( _bytes[ i - 1 ] == 10 && _bytes[ i ] == 10 ) {
+				}
 
-				// we found two line breaks in a row
-				// now we know what the header is
-				_header = this.parseChars( _bytes, 0, i - 2 );
-				// this is were the data starts
-				_data_start = i + 1;
-				break;
+			} // parse the header
 
-			}
 
-		}
+			parseHeader( _header );
 
-		// parse the header
-		parseHeader( _header );
+			var _data = _bytes.subarray( _data_start ); // the data without header
 
-		var _data = _bytes.subarray( _data_start ); // the data without header
-		if ( headerObject.encoding.substring( 0, 2 ) === 'gz' ) {
 
-			// we need to decompress the datastream
-			// here we start the unzipping and get a typed Uint8Array back
-			_data = fflate.gunzipSync( new Uint8Array( _data ) );// eslint-disable-line no-undef
+			if ( headerObject.encoding.substring( 0, 2 ) === 'gz' ) {
 
-		} else if ( headerObject.encoding === 'ascii' || headerObject.encoding === 'text' || headerObject.encoding === 'txt' || headerObject.encoding === 'hex' ) {
+				// we need to decompress the datastream
+				// here we start the unzipping and get a typed Uint8Array back
+				_data = fflate.gunzipSync( new Uint8Array( _data ) ); // eslint-disable-line no-undef
 
-			_data = parseDataAsText( _data );
+			} else if ( headerObject.encoding === 'ascii' || headerObject.encoding === 'text' || headerObject.encoding === 'txt' || headerObject.encoding === 'hex' ) {
 
-		} else if ( headerObject.encoding === 'raw' ) {
+				_data = parseDataAsText( _data );
 
-			//we need to copy the array to create a new array buffer, else we retrieve the original arraybuffer with the header
-			var _copy = new Uint8Array( _data.length );
+			} else if ( headerObject.encoding === 'raw' ) {
 
-			for ( var i = 0; i < _data.length; i ++ ) {
+				//we need to copy the array to create a new array buffer, else we retrieve the original arraybuffer with the header
+				var _copy = new Uint8Array( _data.length );
 
-				_copy[ i ] = _data[ i ];
+				for ( var i = 0; i < _data.length; i ++ ) {
 
-			}
+					_copy[ i ] = _data[ i ];
 
-			_data = _copy;
+				}
 
-		}
+				_data = _copy;
 
-		// .. let's use the underlying array buffer
-		_data = _data.buffer;
-
-		var volume = new THREE.Volume();
-		volume.header = headerObject;
-		//
-		// parse the (unzipped) data to a datastream of the correct type
-		//
-		volume.data = new headerObject.__array( _data );
-		// get the min and max intensities
-		var min_max = volume.computeMinMax();
-		var min = min_max[ 0 ];
-		var max = min_max[ 1 ];
-		// attach the scalar range to the volume
-		volume.windowLow = min;
-		volume.windowHigh = max;
-
-		// get the image dimensions
-		volume.dimensions = [ headerObject.sizes[ 0 ], headerObject.sizes[ 1 ], headerObject.sizes[ 2 ] ];
-		volume.xLength = volume.dimensions[ 0 ];
-		volume.yLength = volume.dimensions[ 1 ];
-		volume.zLength = volume.dimensions[ 2 ];
-		// spacing
-		var spacingX = ( new THREE.Vector3( headerObject.vectors[ 0 ][ 0 ], headerObject.vectors[ 0 ][ 1 ],
-			headerObject.vectors[ 0 ][ 2 ] ) ).length();
-		var spacingY = ( new THREE.Vector3( headerObject.vectors[ 1 ][ 0 ], headerObject.vectors[ 1 ][ 1 ],
-			headerObject.vectors[ 1 ][ 2 ] ) ).length();
-		var spacingZ = ( new THREE.Vector3( headerObject.vectors[ 2 ][ 0 ], headerObject.vectors[ 2 ][ 1 ],
-			headerObject.vectors[ 2 ][ 2 ] ) ).length();
-		volume.spacing = [ spacingX, spacingY, spacingZ ];
-
-
-		// Create IJKtoRAS matrix
-		volume.matrix = new THREE.Matrix4();
-
-		var _spaceX = 1;
-		var _spaceY = 1;
-		var _spaceZ = 1;
-
-		if ( headerObject.space == 'left-posterior-superior' ) {
-
-			_spaceX = - 1;
-			_spaceY = - 1;
-
-		} else if ( headerObject.space === 'left-anterior-superior' ) {
-
-			_spaceX = - 1;
+			} // .. let's use the underlying array buffer
 
-		}
 
+			_data = _data.buffer;
+			var volume = new THREE.Volume();
+			volume.header = headerObject; //
+			// parse the (unzipped) data to a datastream of the correct type
+			//
 
-		if ( ! headerObject.vectors ) {
+			volume.data = new headerObject.__array( _data ); // get the min and max intensities
 
-			volume.matrix.set(
-				_spaceX, 0, 0, 0,
-				0, _spaceY, 0, 0,
-				0, 0, _spaceZ, 0,
-				0, 0, 0, 1 );
+			var min_max = volume.computeMinMax();
+			var min = min_max[ 0 ];
+			var max = min_max[ 1 ]; // attach the scalar range to the volume
 
-		} else {
+			volume.windowLow = min;
+			volume.windowHigh = max; // get the image dimensions
 
-			var v = headerObject.vectors;
+			volume.dimensions = [ headerObject.sizes[ 0 ], headerObject.sizes[ 1 ], headerObject.sizes[ 2 ] ];
+			volume.xLength = volume.dimensions[ 0 ];
+			volume.yLength = volume.dimensions[ 1 ];
+			volume.zLength = volume.dimensions[ 2 ]; // spacing
 
-			volume.matrix.set(
-				_spaceX * v[ 0 ][ 0 ], _spaceX * v[ 1 ][ 0 ], _spaceX * v[ 2 ][ 0 ], 0,
-				_spaceY * v[ 0 ][ 1 ], _spaceY * v[ 1 ][ 1 ], _spaceY * v[ 2 ][ 1 ], 0,
-				_spaceZ * v[ 0 ][ 2 ], _spaceZ * v[ 1 ][ 2 ], _spaceZ * v[ 2 ][ 2 ], 0,
-				0, 0, 0, 1 );
+			var spacingX = new THREE.Vector3( headerObject.vectors[ 0 ][ 0 ], headerObject.vectors[ 0 ][ 1 ], headerObject.vectors[ 0 ][ 2 ] ).length();
+			var spacingY = new THREE.Vector3( headerObject.vectors[ 1 ][ 0 ], headerObject.vectors[ 1 ][ 1 ], headerObject.vectors[ 1 ][ 2 ] ).length();
+			var spacingZ = new THREE.Vector3( headerObject.vectors[ 2 ][ 0 ], headerObject.vectors[ 2 ][ 1 ], headerObject.vectors[ 2 ][ 2 ] ).length();
+			volume.spacing = [ spacingX, spacingY, spacingZ ]; // Create IJKtoRAS matrix
 
-		}
+			volume.matrix = new THREE.Matrix4();
+			var _spaceX = 1;
+			var _spaceY = 1;
+			var _spaceZ = 1;
 
-		volume.inverseMatrix = new THREE.Matrix4();
-		volume.inverseMatrix.copy( volume.matrix ).invert();
-		volume.RASDimensions = ( new THREE.Vector3( volume.xLength, volume.yLength, volume.zLength ) ).applyMatrix4( volume.matrix ).round().toArray().map( Math.abs );
+			if ( headerObject.space == 'left-posterior-superior' ) {
 
-		// .. and set the default threshold
-		// only if the threshold was not already set
-		if ( volume.lowerThreshold === - Infinity ) {
+				_spaceX = - 1;
+				_spaceY = - 1;
 
-			volume.lowerThreshold = min;
+			} else if ( headerObject.space === 'left-anterior-superior' ) {
 
-		}
+				_spaceX = - 1;
 
-		if ( volume.upperThreshold === Infinity ) {
+			}
 
-			volume.upperThreshold = max;
+			if ( ! headerObject.vectors ) {
 
-		}
+				volume.matrix.set( _spaceX, 0, 0, 0, 0, _spaceY, 0, 0, 0, 0, _spaceZ, 0, 0, 0, 0, 1 );
 
-		return volume;
+			} else {
 
-	},
+				var v = headerObject.vectors;
+				volume.matrix.set( _spaceX * v[ 0 ][ 0 ], _spaceX * v[ 1 ][ 0 ], _spaceX * v[ 2 ][ 0 ], 0, _spaceY * v[ 0 ][ 1 ], _spaceY * v[ 1 ][ 1 ], _spaceY * v[ 2 ][ 1 ], 0, _spaceZ * v[ 0 ][ 2 ], _spaceZ * v[ 1 ][ 2 ], _spaceZ * v[ 2 ][ 2 ], 0, 0, 0, 0, 1 );
 
-	parseChars: function ( array, start, end ) {
+			}
 
-		// without borders, use the whole array
-		if ( start === undefined ) {
+			volume.inverseMatrix = new THREE.Matrix4();
+			volume.inverseMatrix.copy( volume.matrix ).invert();
+			volume.RASDimensions = new THREE.Vector3( volume.xLength, volume.yLength, volume.zLength ).applyMatrix4( volume.matrix ).round().toArray().map( Math.abs ); // .. and set the default threshold
+			// only if the threshold was not already set
 
-			start = 0;
+			if ( volume.lowerThreshold === - Infinity ) {
 
-		}
+				volume.lowerThreshold = min;
 
-		if ( end === undefined ) {
+			}
 
-			end = array.length;
+			if ( volume.upperThreshold === Infinity ) {
 
-		}
+				volume.upperThreshold = max;
 
-		var output = '';
-		// create and append the chars
-		var i = 0;
-		for ( i = start; i < end; ++ i ) {
+			}
 
-			output += String.fromCharCode( array[ i ] );
+			return volume;
 
-		}
+		},
+		parseChars: function ( array, start, end ) {
 
-		return output;
+			// without borders, use the whole array
+			if ( start === undefined ) {
 
-	},
+				start = 0;
 
-	fieldFunctions: {
+			}
 
-		type: function ( data ) {
+			if ( end === undefined ) {
 
-			switch ( data ) {
+				end = array.length;
 
-				case 'uchar':
-				case 'unsigned char':
-				case 'uint8':
-				case 'uint8_t':
-					this.__array = Uint8Array;
-					break;
-				case 'signed char':
-				case 'int8':
-				case 'int8_t':
-					this.__array = Int8Array;
-					break;
-				case 'short':
-				case 'short int':
-				case 'signed short':
-				case 'signed short int':
-				case 'int16':
-				case 'int16_t':
-					this.__array = Int16Array;
-					break;
-				case 'ushort':
-				case 'unsigned short':
-				case 'unsigned short int':
-				case 'uint16':
-				case 'uint16_t':
-					this.__array = Uint16Array;
-					break;
-				case 'int':
-				case 'signed int':
-				case 'int32':
-				case 'int32_t':
-					this.__array = Int32Array;
-					break;
-				case 'uint':
-				case 'unsigned int':
-				case 'uint32':
-				case 'uint32_t':
-					this.__array = Uint32Array;
-					break;
-				case 'float':
-					this.__array = Float32Array;
-					break;
-				case 'double':
-					this.__array = Float64Array;
-					break;
-				default:
-					throw new Error( 'Unsupported NRRD data type: ' + data );
+			}
+
+			var output = ''; // create and append the chars
+
+			var i = 0;
+
+			for ( i = start; i < end; ++ i ) {
+
+				output += String.fromCharCode( array[ i ] );
 
 			}
 
-			return this.type = data;
+			return output;
 
 		},
+		fieldFunctions: {
+			type: function ( data ) {
+
+				switch ( data ) {
+
+					case 'uchar':
+					case 'unsigned char':
+					case 'uint8':
+					case 'uint8_t':
+						this.__array = Uint8Array;
+						break;
+
+					case 'signed char':
+					case 'int8':
+					case 'int8_t':
+						this.__array = Int8Array;
+						break;
+
+					case 'short':
+					case 'short int':
+					case 'signed short':
+					case 'signed short int':
+					case 'int16':
+					case 'int16_t':
+						this.__array = Int16Array;
+						break;
+
+					case 'ushort':
+					case 'unsigned short':
+					case 'unsigned short int':
+					case 'uint16':
+					case 'uint16_t':
+						this.__array = Uint16Array;
+						break;
+
+					case 'int':
+					case 'signed int':
+					case 'int32':
+					case 'int32_t':
+						this.__array = Int32Array;
+						break;
+
+					case 'uint':
+					case 'unsigned int':
+					case 'uint32':
+					case 'uint32_t':
+						this.__array = Uint32Array;
+						break;
+
+					case 'float':
+						this.__array = Float32Array;
+						break;
+
+					case 'double':
+						this.__array = Float64Array;
+						break;
+
+					default:
+						throw new Error( 'Unsupported NRRD data type: ' + data );
 
-		endian: function ( data ) {
+				}
 
-			return this.endian = data;
+				return this.type = data;
 
-		},
+			},
+			endian: function ( data ) {
 
-		encoding: function ( data ) {
+				return this.endian = data;
 
-			return this.encoding = data;
+			},
+			encoding: function ( data ) {
 
-		},
+				return this.encoding = data;
 
-		dimension: function ( data ) {
+			},
+			dimension: function ( data ) {
 
-			return this.dim = parseInt( data, 10 );
+				return this.dim = parseInt( data, 10 );
 
-		},
+			},
+			sizes: function ( data ) {
 
-		sizes: function ( data ) {
+				var i;
+				return this.sizes = function () {
 
-			var i;
-			return this.sizes = ( function () {
+					var _i, _len, _ref, _results;
 
-				var _i, _len, _ref, _results;
-				_ref = data.split( /\s+/ );
-				_results = [];
+					_ref = data.split( /\s+/ );
+					_results = [];
 
-				for ( _i = 0, _len = _ref.length; _i < _len; _i ++ ) {
+					for ( _i = 0, _len = _ref.length; _i < _len; _i ++ ) {
 
-					i = _ref[ _i ];
-					_results.push( parseInt( i, 10 ) );
+						i = _ref[ _i ];
 
-				}
+						_results.push( parseInt( i, 10 ) );
 
-				return _results;
+					}
 
-			} )();
+					return _results;
 
-		},
+				}();
 
-		space: function ( data ) {
+			},
+			space: function ( data ) {
 
-			return this.space = data;
+				return this.space = data;
 
-		},
+			},
+			'space origin': function ( data ) {
 
-		'space origin': function ( data ) {
+				return this.space_origin = data.split( '(' )[ 1 ].split( ')' )[ 0 ].split( ',' );
 
-			return this.space_origin = data.split( '(' )[ 1 ].split( ')' )[ 0 ].split( ',' );
+			},
+			'space directions': function ( data ) {
 
-		},
+				var f, parts, v;
+				parts = data.match( /\(.*?\)/g );
+				return this.vectors = function () {
 
-		'space directions': function ( data ) {
+					var _i, _len, _results;
 
-			var f, parts, v;
-			parts = data.match( /\(.*?\)/g );
-			return this.vectors = ( function () {
+					_results = [];
 
-				var _i, _len, _results;
-				_results = [];
+					for ( _i = 0, _len = parts.length; _i < _len; _i ++ ) {
 
-				for ( _i = 0, _len = parts.length; _i < _len; _i ++ ) {
+						v = parts[ _i ];
 
-					v = parts[ _i ];
-					_results.push( ( function () {
+						_results.push( function () {
 
-						var _j, _len2, _ref, _results2;
-						_ref = v.slice( 1, - 1 ).split( /,/ );
-						_results2 = [];
+							var _j, _len2, _ref, _results2;
 
-						for ( _j = 0, _len2 = _ref.length; _j < _len2; _j ++ ) {
+							_ref = v.slice( 1, - 1 ).split( /,/ );
+							_results2 = [];
 
-							f = _ref[ _j ];
-							_results2.push( parseFloat( f ) );
+							for ( _j = 0, _len2 = _ref.length; _j < _len2; _j ++ ) {
 
-						}
+								f = _ref[ _j ];
 
-						return _results2;
+								_results2.push( parseFloat( f ) );
 
-					} )() );
+							}
 
-				}
+							return _results2;
+
+						}() );
 
-				return _results;
+					}
 
-			} )();
+					return _results;
 
-		},
+				}();
 
-		spacings: function ( data ) {
+			},
+			spacings: function ( data ) {
 
-			var f, parts;
-			parts = data.split( /\s+/ );
-			return this.spacings = ( function () {
+				var f, parts;
+				parts = data.split( /\s+/ );
+				return this.spacings = function () {
 
-				var _i, _len, _results = [];
+					var _i,
+						_len,
+						_results = [];
 
-				for ( _i = 0, _len = parts.length; _i < _len; _i ++ ) {
+					for ( _i = 0, _len = parts.length; _i < _len; _i ++ ) {
 
-					f = parts[ _i ];
-					_results.push( parseFloat( f ) );
+						f = parts[ _i ];
 
-				}
+						_results.push( parseFloat( f ) );
 
-				return _results;
+					}
 
-			} )();
+					return _results;
 
+				}();
+
+			}
 		}
-	}
+	} );
+
+	THREE.NRRDLoader = NRRDLoader;
 
-} );
+} )();

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 380 - 480
examples/js/loaders/OBJLoader.js


+ 213 - 251
examples/js/loaders/PCDLoader.js

@@ -1,390 +1,352 @@
-THREE.PCDLoader = function ( manager ) {
+( function () {
 
-	THREE.Loader.call( this, manager );
+	var PCDLoader = function ( manager ) {
 
-	this.littleEndian = true;
+		THREE.Loader.call( this, manager );
+		this.littleEndian = true;
 
-};
+	};
 
+	PCDLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+		constructor: PCDLoader,
+		load: function ( url, onLoad, onProgress, onError ) {
 
-THREE.PCDLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+			var scope = this;
+			var loader = new THREE.FileLoader( scope.manager );
+			loader.setPath( scope.path );
+			loader.setResponseType( 'arraybuffer' );
+			loader.setRequestHeader( scope.requestHeader );
+			loader.setWithCredentials( scope.withCredentials );
+			loader.load( url, function ( data ) {
 
-	constructor: THREE.PCDLoader,
+				try {
 
-	load: function ( url, onLoad, onProgress, onError ) {
+					onLoad( scope.parse( data, url ) );
 
-		var scope = this;
+				} catch ( e ) {
 
-		var loader = new THREE.FileLoader( scope.manager );
-		loader.setPath( scope.path );
-		loader.setResponseType( 'arraybuffer' );
-		loader.setRequestHeader( scope.requestHeader );
-		loader.setWithCredentials( scope.withCredentials );
-		loader.load( url, function ( data ) {
+					if ( onError ) {
 
-			try {
+						onError( e );
 
-				onLoad( scope.parse( data, url ) );
+					} else {
 
-			} catch ( e ) {
+						console.error( e );
 
-				if ( onError ) {
-
-					onError( e );
-
-				} else {
+					}
 
-					console.error( e );
+					scope.manager.itemError( url );
 
 				}
 
-				scope.manager.itemError( url );
+			}, onProgress, onError );
 
-			}
+		},
+		parse: function ( data, url ) {
 
-		}, onProgress, onError );
+			// from https://gitlab.com/taketwo/three-pcd-loader/blob/master/decompress-lzf.js
+			function decompressLZF( inData, outLength ) {
 
-	},
+				var inLength = inData.length;
+				var outData = new Uint8Array( outLength );
+				var inPtr = 0;
+				var outPtr = 0;
+				var ctrl;
+				var len;
+				var ref;
 
-	parse: function ( data, url ) {
+				do {
 
-		// from https://gitlab.com/taketwo/three-pcd-loader/blob/master/decompress-lzf.js
+					ctrl = inData[ inPtr ++ ];
 
-		function decompressLZF( inData, outLength ) {
+					if ( ctrl < 1 << 5 ) {
 
-			var inLength = inData.length;
-			var outData = new Uint8Array( outLength );
-			var inPtr = 0;
-			var outPtr = 0;
-			var ctrl;
-			var len;
-			var ref;
-			do {
+						ctrl ++;
+						if ( outPtr + ctrl > outLength ) throw new Error( 'Output buffer is not large enough' );
+						if ( inPtr + ctrl > inLength ) throw new Error( 'Invalid compressed data' );
 
-				ctrl = inData[ inPtr ++ ];
-				if ( ctrl < ( 1 << 5 ) ) {
+						do {
 
-					ctrl ++;
-					if ( outPtr + ctrl > outLength ) throw new Error( 'Output buffer is not large enough' );
-					if ( inPtr + ctrl > inLength ) throw new Error( 'Invalid compressed data' );
-					do {
+							outData[ outPtr ++ ] = inData[ inPtr ++ ];
 
-						outData[ outPtr ++ ] = inData[ inPtr ++ ];
+						} while ( -- ctrl );
 
-					} while ( -- ctrl );
+					} else {
 
-				} else {
-
-					len = ctrl >> 5;
-					ref = outPtr - ( ( ctrl & 0x1f ) << 8 ) - 1;
-					if ( inPtr >= inLength ) throw new Error( 'Invalid compressed data' );
-					if ( len === 7 ) {
-
-						len += inData[ inPtr ++ ];
+						len = ctrl >> 5;
+						ref = outPtr - ( ( ctrl & 0x1f ) << 8 ) - 1;
 						if ( inPtr >= inLength ) throw new Error( 'Invalid compressed data' );
 
-					}
+						if ( len === 7 ) {
 
-					ref -= inData[ inPtr ++ ];
-					if ( outPtr + len + 2 > outLength ) throw new Error( 'Output buffer is not large enough' );
-					if ( ref < 0 ) throw new Error( 'Invalid compressed data' );
-					if ( ref >= outPtr ) throw new Error( 'Invalid compressed data' );
-					do {
+							len += inData[ inPtr ++ ];
+							if ( inPtr >= inLength ) throw new Error( 'Invalid compressed data' );
 
-						outData[ outPtr ++ ] = outData[ ref ++ ];
+						}
 
-					} while ( -- len + 2 );
+						ref -= inData[ inPtr ++ ];
+						if ( outPtr + len + 2 > outLength ) throw new Error( 'Output buffer is not large enough' );
+						if ( ref < 0 ) throw new Error( 'Invalid compressed data' );
+						if ( ref >= outPtr ) throw new Error( 'Invalid compressed data' );
 
-				}
-
-			} while ( inPtr < inLength );
-
-			return outData;
-
-		}
+						do {
 
-		function parseHeader( data ) {
+							outData[ outPtr ++ ] = outData[ ref ++ ];
 
-			var PCDheader = {};
-			var result1 = data.search( /[\r\n]DATA\s(\S*)\s/i );
-			var result2 = /[\r\n]DATA\s(\S*)\s/i.exec( data.substr( result1 - 1 ) );
+						} while ( -- len + 2 );
 
-			PCDheader.data = result2[ 1 ];
-			PCDheader.headerLen = result2[ 0 ].length + result1;
-			PCDheader.str = data.substr( 0, PCDheader.headerLen );
+					}
 
-			// remove comments
+				} while ( inPtr < inLength );
 
-			PCDheader.str = PCDheader.str.replace( /\#.*/gi, '' );
+				return outData;
 
-			// parse
+			}
 
-			PCDheader.version = /VERSION (.*)/i.exec( PCDheader.str );
-			PCDheader.fields = /FIELDS (.*)/i.exec( PCDheader.str );
-			PCDheader.size = /SIZE (.*)/i.exec( PCDheader.str );
-			PCDheader.type = /TYPE (.*)/i.exec( PCDheader.str );
-			PCDheader.count = /COUNT (.*)/i.exec( PCDheader.str );
-			PCDheader.width = /WIDTH (.*)/i.exec( PCDheader.str );
-			PCDheader.height = /HEIGHT (.*)/i.exec( PCDheader.str );
-			PCDheader.viewpoint = /VIEWPOINT (.*)/i.exec( PCDheader.str );
-			PCDheader.points = /POINTS (.*)/i.exec( PCDheader.str );
+			function parseHeader( data ) {
 
-			// evaluate
+				var PCDheader = {};
+				var result1 = data.search( /[\r\n]DATA\s(\S*)\s/i );
+				var result2 = /[\r\n]DATA\s(\S*)\s/i.exec( data.substr( result1 - 1 ) );
+				PCDheader.data = result2[ 1 ];
+				PCDheader.headerLen = result2[ 0 ].length + result1;
+				PCDheader.str = data.substr( 0, PCDheader.headerLen ); // remove comments
 
-			if ( PCDheader.version !== null )
-				PCDheader.version = parseFloat( PCDheader.version[ 1 ] );
+				PCDheader.str = PCDheader.str.replace( /\#.*/gi, '' ); // parse
 
-			if ( PCDheader.fields !== null )
-				PCDheader.fields = PCDheader.fields[ 1 ].split( ' ' );
+				PCDheader.version = /VERSION (.*)/i.exec( PCDheader.str );
+				PCDheader.fields = /FIELDS (.*)/i.exec( PCDheader.str );
+				PCDheader.size = /SIZE (.*)/i.exec( PCDheader.str );
+				PCDheader.type = /TYPE (.*)/i.exec( PCDheader.str );
+				PCDheader.count = /COUNT (.*)/i.exec( PCDheader.str );
+				PCDheader.width = /WIDTH (.*)/i.exec( PCDheader.str );
+				PCDheader.height = /HEIGHT (.*)/i.exec( PCDheader.str );
+				PCDheader.viewpoint = /VIEWPOINT (.*)/i.exec( PCDheader.str );
+				PCDheader.points = /POINTS (.*)/i.exec( PCDheader.str ); // evaluate
 
-			if ( PCDheader.type !== null )
-				PCDheader.type = PCDheader.type[ 1 ].split( ' ' );
+				if ( PCDheader.version !== null ) PCDheader.version = parseFloat( PCDheader.version[ 1 ] );
+				if ( PCDheader.fields !== null ) PCDheader.fields = PCDheader.fields[ 1 ].split( ' ' );
+				if ( PCDheader.type !== null ) PCDheader.type = PCDheader.type[ 1 ].split( ' ' );
+				if ( PCDheader.width !== null ) PCDheader.width = parseInt( PCDheader.width[ 1 ] );
+				if ( PCDheader.height !== null ) PCDheader.height = parseInt( PCDheader.height[ 1 ] );
+				if ( PCDheader.viewpoint !== null ) PCDheader.viewpoint = PCDheader.viewpoint[ 1 ];
+				if ( PCDheader.points !== null ) PCDheader.points = parseInt( PCDheader.points[ 1 ], 10 );
+				if ( PCDheader.points === null ) PCDheader.points = PCDheader.width * PCDheader.height;
 
-			if ( PCDheader.width !== null )
-				PCDheader.width = parseInt( PCDheader.width[ 1 ] );
+				if ( PCDheader.size !== null ) {
 
-			if ( PCDheader.height !== null )
-				PCDheader.height = parseInt( PCDheader.height[ 1 ] );
+					PCDheader.size = PCDheader.size[ 1 ].split( ' ' ).map( function ( x ) {
 
-			if ( PCDheader.viewpoint !== null )
-				PCDheader.viewpoint = PCDheader.viewpoint[ 1 ];
+						return parseInt( x, 10 );
 
-			if ( PCDheader.points !== null )
-				PCDheader.points = parseInt( PCDheader.points[ 1 ], 10 );
+					} );
 
-			if ( PCDheader.points === null )
-				PCDheader.points = PCDheader.width * PCDheader.height;
+				}
 
-			if ( PCDheader.size !== null ) {
+				if ( PCDheader.count !== null ) {
 
-				PCDheader.size = PCDheader.size[ 1 ].split( ' ' ).map( function ( x ) {
+					PCDheader.count = PCDheader.count[ 1 ].split( ' ' ).map( function ( x ) {
 
-					return parseInt( x, 10 );
+						return parseInt( x, 10 );
 
-				} );
+					} );
 
-			}
+				} else {
 
-			if ( PCDheader.count !== null ) {
+					PCDheader.count = [];
 
-				PCDheader.count = PCDheader.count[ 1 ].split( ' ' ).map( function ( x ) {
+					for ( var i = 0, l = PCDheader.fields.length; i < l; i ++ ) {
 
-					return parseInt( x, 10 );
+						PCDheader.count.push( 1 );
 
-				} );
+					}
 
-			} else {
+				}
 
-				PCDheader.count = [];
+				PCDheader.offset = {};
+				var sizeSum = 0;
 
 				for ( var i = 0, l = PCDheader.fields.length; i < l; i ++ ) {
 
-					PCDheader.count.push( 1 );
-
-				}
-
-			}
-
-			PCDheader.offset = {};
+					if ( PCDheader.data === 'ascii' ) {
 
-			var sizeSum = 0;
+						PCDheader.offset[ PCDheader.fields[ i ] ] = i;
 
-			for ( var i = 0, l = PCDheader.fields.length; i < l; i ++ ) {
+					} else {
 
-				if ( PCDheader.data === 'ascii' ) {
+						PCDheader.offset[ PCDheader.fields[ i ] ] = sizeSum;
+						sizeSum += PCDheader.size[ i ] * PCDheader.count[ i ];
 
-					PCDheader.offset[ PCDheader.fields[ i ] ] = i;
+					}
 
-				} else {
+				} // for binary only
 
-					PCDheader.offset[ PCDheader.fields[ i ] ] = sizeSum;
-					sizeSum += PCDheader.size[ i ] * PCDheader.count[ i ];
 
-				}
+				PCDheader.rowSize = sizeSum;
+				return PCDheader;
 
 			}
 
-			// for binary only
-
-			PCDheader.rowSize = sizeSum;
-
-			return PCDheader;
-
-		}
-
-		var textData = THREE.LoaderUtils.decodeText( new Uint8Array( data ) );
-
-		// parse header (always ascii format)
-
-		var PCDheader = parseHeader( textData );
-
-		// parse data
+			var textData = THREE.LoaderUtils.decodeText( new Uint8Array( data ) ); // parse header (always ascii format)
 
-		var position = [];
-		var normal = [];
-		var color = [];
+			var PCDheader = parseHeader( textData ); // parse data
 
-		// ascii
+			var position = [];
+			var normal = [];
+			var color = []; // ascii
 
-		if ( PCDheader.data === 'ascii' ) {
+			if ( PCDheader.data === 'ascii' ) {
 
-			var offset = PCDheader.offset;
-			var pcdData = textData.substr( PCDheader.headerLen );
-			var lines = pcdData.split( '\n' );
+				var offset = PCDheader.offset;
+				var pcdData = textData.substr( PCDheader.headerLen );
+				var lines = pcdData.split( '\n' );
 
-			for ( var i = 0, l = lines.length; i < l; i ++ ) {
+				for ( var i = 0, l = lines.length; i < l; i ++ ) {
 
-				if ( lines[ i ] === '' ) continue;
+					if ( lines[ i ] === '' ) continue;
+					var line = lines[ i ].split( ' ' );
 
-				var line = lines[ i ].split( ' ' );
+					if ( offset.x !== undefined ) {
 
-				if ( offset.x !== undefined ) {
+						position.push( parseFloat( line[ offset.x ] ) );
+						position.push( parseFloat( line[ offset.y ] ) );
+						position.push( parseFloat( line[ offset.z ] ) );
 
-					position.push( parseFloat( line[ offset.x ] ) );
-					position.push( parseFloat( line[ offset.y ] ) );
-					position.push( parseFloat( line[ offset.z ] ) );
+					}
 
-				}
+					if ( offset.rgb !== undefined ) {
 
-				if ( offset.rgb !== undefined ) {
+						var rgb = parseFloat( line[ offset.rgb ] );
+						var r = rgb >> 16 & 0x0000ff;
+						var g = rgb >> 8 & 0x0000ff;
+						var b = rgb >> 0 & 0x0000ff;
+						color.push( r / 255, g / 255, b / 255 );
 
-					var rgb = parseFloat( line[ offset.rgb ] );
-					var r = ( rgb >> 16 ) & 0x0000ff;
-					var g = ( rgb >> 8 ) & 0x0000ff;
-					var b = ( rgb >> 0 ) & 0x0000ff;
-					color.push( r / 255, g / 255, b / 255 );
+					}
 
-				}
+					if ( offset.normal_x !== undefined ) {
 
-				if ( offset.normal_x !== undefined ) {
+						normal.push( parseFloat( line[ offset.normal_x ] ) );
+						normal.push( parseFloat( line[ offset.normal_y ] ) );
+						normal.push( parseFloat( line[ offset.normal_z ] ) );
 
-					normal.push( parseFloat( line[ offset.normal_x ] ) );
-					normal.push( parseFloat( line[ offset.normal_y ] ) );
-					normal.push( parseFloat( line[ offset.normal_z ] ) );
+					}
 
 				}
 
-			}
-
-		}
+			} // binary-compressed
+			// normally data in PCD files are organized as array of structures: XYZRGBXYZRGB
+			// binary compressed PCD files organize their data as structure of arrays: XXYYZZRGBRGB
+			// that requires a totally different parsing approach compared to non-compressed data
 
-		// binary-compressed
 
-		// normally data in PCD files are organized as array of structures: XYZRGBXYZRGB
-		// binary compressed PCD files organize their data as structure of arrays: XXYYZZRGBRGB
-		// that requires a totally different parsing approach compared to non-compressed data
+			if ( PCDheader.data === 'binary_compressed' ) {
 
-		if ( PCDheader.data === 'binary_compressed' ) {
+				var sizes = new Uint32Array( data.slice( PCDheader.headerLen, PCDheader.headerLen + 8 ) );
+				var compressedSize = sizes[ 0 ];
+				var decompressedSize = sizes[ 1 ];
+				var decompressed = decompressLZF( new Uint8Array( data, PCDheader.headerLen + 8, compressedSize ), decompressedSize );
+				var dataview = new DataView( decompressed.buffer );
+				var offset = PCDheader.offset;
 
-			var sizes = new Uint32Array( data.slice( PCDheader.headerLen, PCDheader.headerLen + 8 ) );
-			var compressedSize = sizes[ 0 ];
-			var decompressedSize = sizes[ 1 ];
-			var decompressed = decompressLZF( new Uint8Array( data, PCDheader.headerLen + 8, compressedSize ), decompressedSize );
-			var dataview = new DataView( decompressed.buffer );
+				for ( var i = 0; i < PCDheader.points; i ++ ) {
 
-			var offset = PCDheader.offset;
+					if ( offset.x !== undefined ) {
 
-			for ( var i = 0; i < PCDheader.points; i ++ ) {
+						position.push( dataview.getFloat32( PCDheader.points * offset.x + PCDheader.size[ 0 ] * i, this.littleEndian ) );
+						position.push( dataview.getFloat32( PCDheader.points * offset.y + PCDheader.size[ 1 ] * i, this.littleEndian ) );
+						position.push( dataview.getFloat32( PCDheader.points * offset.z + PCDheader.size[ 2 ] * i, this.littleEndian ) );
 
-				if ( offset.x !== undefined ) {
+					}
 
-					position.push( dataview.getFloat32( ( PCDheader.points * offset.x ) + PCDheader.size[ 0 ] * i, this.littleEndian ) );
-					position.push( dataview.getFloat32( ( PCDheader.points * offset.y ) + PCDheader.size[ 1 ] * i, this.littleEndian ) );
-					position.push( dataview.getFloat32( ( PCDheader.points * offset.z ) + PCDheader.size[ 2 ] * i, this.littleEndian ) );
+					if ( offset.rgb !== undefined ) {
 
-				}
+						color.push( dataview.getUint8( PCDheader.points * offset.rgb + PCDheader.size[ 3 ] * i + 0 ) / 255.0 );
+						color.push( dataview.getUint8( PCDheader.points * offset.rgb + PCDheader.size[ 3 ] * i + 1 ) / 255.0 );
+						color.push( dataview.getUint8( PCDheader.points * offset.rgb + PCDheader.size[ 3 ] * i + 2 ) / 255.0 );
 
-				if ( offset.rgb !== undefined ) {
+					}
 
-					color.push( dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ 3 ] * i + 0 ) / 255.0 );
-					color.push( dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ 3 ] * i + 1 ) / 255.0 );
-					color.push( dataview.getUint8( ( PCDheader.points * offset.rgb ) + PCDheader.size[ 3 ] * i + 2 ) / 255.0 );
+					if ( offset.normal_x !== undefined ) {
 
-				}
+						normal.push( dataview.getFloat32( PCDheader.points * offset.normal_x + PCDheader.size[ 4 ] * i, this.littleEndian ) );
+						normal.push( dataview.getFloat32( PCDheader.points * offset.normal_y + PCDheader.size[ 5 ] * i, this.littleEndian ) );
+						normal.push( dataview.getFloat32( PCDheader.points * offset.normal_z + PCDheader.size[ 6 ] * i, this.littleEndian ) );
 
-				if ( offset.normal_x !== undefined ) {
-
-					normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_x ) + PCDheader.size[ 4 ] * i, this.littleEndian ) );
-					normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_y ) + PCDheader.size[ 5 ] * i, this.littleEndian ) );
-					normal.push( dataview.getFloat32( ( PCDheader.points * offset.normal_z ) + PCDheader.size[ 6 ] * i, this.littleEndian ) );
+					}
 
 				}
 
-			}
+			} // binary
 
-		}
 
-		// binary
+			if ( PCDheader.data === 'binary' ) {
 
-		if ( PCDheader.data === 'binary' ) {
+				var dataview = new DataView( data, PCDheader.headerLen );
+				var offset = PCDheader.offset;
 
-			var dataview = new DataView( data, PCDheader.headerLen );
-			var offset = PCDheader.offset;
+				for ( var i = 0, row = 0; i < PCDheader.points; i ++, row += PCDheader.rowSize ) {
 
-			for ( var i = 0, row = 0; i < PCDheader.points; i ++, row += PCDheader.rowSize ) {
+					if ( offset.x !== undefined ) {
 
-				if ( offset.x !== undefined ) {
+						position.push( dataview.getFloat32( row + offset.x, this.littleEndian ) );
+						position.push( dataview.getFloat32( row + offset.y, this.littleEndian ) );
+						position.push( dataview.getFloat32( row + offset.z, this.littleEndian ) );
 
-					position.push( dataview.getFloat32( row + offset.x, this.littleEndian ) );
-					position.push( dataview.getFloat32( row + offset.y, this.littleEndian ) );
-					position.push( dataview.getFloat32( row + offset.z, this.littleEndian ) );
+					}
 
-				}
+					if ( offset.rgb !== undefined ) {
 
-				if ( offset.rgb !== undefined ) {
+						color.push( dataview.getUint8( row + offset.rgb + 2 ) / 255.0 );
+						color.push( dataview.getUint8( row + offset.rgb + 1 ) / 255.0 );
+						color.push( dataview.getUint8( row + offset.rgb + 0 ) / 255.0 );
 
-					color.push( dataview.getUint8( row + offset.rgb + 2 ) / 255.0 );
-					color.push( dataview.getUint8( row + offset.rgb + 1 ) / 255.0 );
-					color.push( dataview.getUint8( row + offset.rgb + 0 ) / 255.0 );
+					}
 
-				}
+					if ( offset.normal_x !== undefined ) {
 
-				if ( offset.normal_x !== undefined ) {
+						normal.push( dataview.getFloat32( row + offset.normal_x, this.littleEndian ) );
+						normal.push( dataview.getFloat32( row + offset.normal_y, this.littleEndian ) );
+						normal.push( dataview.getFloat32( row + offset.normal_z, this.littleEndian ) );
 
-					normal.push( dataview.getFloat32( row + offset.normal_x, this.littleEndian ) );
-					normal.push( dataview.getFloat32( row + offset.normal_y, this.littleEndian ) );
-					normal.push( dataview.getFloat32( row + offset.normal_z, this.littleEndian ) );
+					}
 
 				}
 
-			}
-
-		}
+			} // build geometry
 
-		// build geometry
 
-		var geometry = new THREE.BufferGeometry();
+			var geometry = new THREE.BufferGeometry();
+			if ( position.length > 0 ) geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( position, 3 ) );
+			if ( normal.length > 0 ) geometry.setAttribute( 'normal', new THREE.Float32BufferAttribute( normal, 3 ) );
+			if ( color.length > 0 ) geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( color, 3 ) );
+			geometry.computeBoundingSphere(); // build material
 
-		if ( position.length > 0 ) geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( position, 3 ) );
-		if ( normal.length > 0 ) geometry.setAttribute( 'normal', new THREE.Float32BufferAttribute( normal, 3 ) );
-		if ( color.length > 0 ) geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( color, 3 ) );
+			var material = new THREE.PointsMaterial( {
+				size: 0.005
+			} );
 
-		geometry.computeBoundingSphere();
+			if ( color.length > 0 ) {
 
-		// build material
+				material.vertexColors = true;
 
-		var material = new THREE.PointsMaterial( { size: 0.005 } );
+			} else {
 
-		if ( color.length > 0 ) {
+				material.color.setHex( Math.random() * 0xffffff );
 
-			material.vertexColors = true;
+			} // build point cloud
 
-		} else {
 
-			material.color.setHex( Math.random() * 0xffffff );
+			var mesh = new THREE.Points( geometry, material );
+			var name = url.split( '' ).reverse().join( '' );
+			name = /([^\/]*)/.exec( name );
+			name = name[ 1 ].split( '' ).reverse().join( '' );
+			mesh.name = name;
+			return mesh;
 
 		}
+	} );
 
-		// build point cloud
-
-		var mesh = new THREE.Points( geometry, material );
-		var name = url.split( '' ).reverse().join( '' );
-		name = /([^\/]*)/.exec( name );
-		name = name[ 1 ].split( '' ).reverse().join( '' );
-		mesh.name = name;
-
-		return mesh;
-
-	}
+	THREE.PCDLoader = PCDLoader;
 
-} );
+} )();

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 99 - 123
examples/js/loaders/PDBLoader.js


+ 309 - 301
examples/js/loaders/PLYLoader.js

@@ -1,11 +1,13 @@
-/**
+( function () {
+
+	/**
  * Description: A THREE loader for PLY ASCII files (known as the Polygon
  * File Format or the Stanford Triangle Format).
  *
  * Limitations: ASCII decoding assumes file is UTF-8.
  *
  * Usage:
- *	var loader = new THREE.PLYLoader();
+ *	var loader = new PLYLoader();
  *	loader.load('./models/ply/ascii/dolphins.ply', function (geometry) {
  *
  *		scene.add( new THREE.Mesh( geometry ) );
@@ -24,500 +26,506 @@
  *
  */
 
+	var PLYLoader = function ( manager ) {
 
-THREE.PLYLoader = function ( manager ) {
-
-	THREE.Loader.call( this, manager );
-
-	this.propertyNameMapping = {};
+		THREE.Loader.call( this, manager );
+		this.propertyNameMapping = {};
 
-};
+	};
 
-THREE.PLYLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+	PLYLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+		constructor: PLYLoader,
+		load: function ( url, onLoad, onProgress, onError ) {
 
-	constructor: THREE.PLYLoader,
+			var scope = this;
+			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 ( text ) {
 
-	load: function ( url, onLoad, onProgress, onError ) {
+				try {
 
-		var scope = this;
+					onLoad( scope.parse( text ) );
 
-		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 ( text ) {
+				} catch ( e ) {
 
-			try {
+					if ( onError ) {
 
-				onLoad( scope.parse( text ) );
+						onError( e );
 
-			} catch ( e ) {
+					} else {
 
-				if ( onError ) {
+						console.error( e );
 
-					onError( e );
-
-				} else {
+					}
 
-					console.error( e );
+					scope.manager.itemError( url );
 
 				}
 
-				scope.manager.itemError( url );
-
-			}
-
-		}, onProgress, onError );
-
-	},
-
-	setPropertyNameMapping: function ( mapping ) {
-
-		this.propertyNameMapping = mapping;
-
-	},
+			}, onProgress, onError );
 
-	parse: function ( data ) {
+		},
+		setPropertyNameMapping: function ( mapping ) {
 
-		function parseHeader( data ) {
+			this.propertyNameMapping = mapping;
 
-			var patternHeader = /ply([\s\S]*)end_header\r?\n/;
-			var headerText = '';
-			var headerLength = 0;
-			var result = patternHeader.exec( data );
+		},
+		parse: function ( data ) {
 
-			if ( result !== null ) {
+			function parseHeader( data ) {
 
-				headerText = result[ 1 ];
-				headerLength = new Blob( [ result[ 0 ] ] ).size;
+				var patternHeader = /ply([\s\S]*)end_header\r?\n/;
+				var headerText = '';
+				var headerLength = 0;
+				var result = patternHeader.exec( data );
 
-			}
-
-			var header = {
-				comments: [],
-				elements: [],
-				headerLength: headerLength,
-				objInfo: ''
-			};
-
-			var lines = headerText.split( '\n' );
-			var currentElement;
-			var lineType, lineValues;
-
-			function make_ply_element_property( propertValues, propertyNameMapping ) {
-
-				var property = { type: propertValues[ 0 ] };
-
-				if ( property.type === 'list' ) {
+				if ( result !== null ) {
 
-					property.name = propertValues[ 3 ];
-					property.countType = propertValues[ 1 ];
-					property.itemType = propertValues[ 2 ];
-
-				} else {
-
-					property.name = propertValues[ 1 ];
+					headerText = result[ 1 ];
+					headerLength = new Blob( [ result[ 0 ] ] ).size;
 
 				}
 
-				if ( property.name in propertyNameMapping ) {
-
-					property.name = propertyNameMapping[ property.name ];
+				var header = {
+					comments: [],
+					elements: [],
+					headerLength: headerLength,
+					objInfo: ''
+				};
+				var lines = headerText.split( '\n' );
+				var currentElement;
+				var lineType, lineValues;
 
-				}
-
-				return property;
+				function make_ply_element_property( propertValues, propertyNameMapping ) {
 
-			}
+					var property = {
+						type: propertValues[ 0 ]
+					};
 
-			for ( var i = 0; i < lines.length; i ++ ) {
+					if ( property.type === 'list' ) {
 
-				var line = lines[ i ];
-				line = line.trim();
+						property.name = propertValues[ 3 ];
+						property.countType = propertValues[ 1 ];
+						property.itemType = propertValues[ 2 ];
 
-				if ( line === '' ) continue;
+					} else {
 
-				lineValues = line.split( /\s+/ );
-				lineType = lineValues.shift();
-				line = lineValues.join( ' ' );
+						property.name = propertValues[ 1 ];
 
-				switch ( lineType ) {
+					}
 
-					case 'format':
+					if ( property.name in propertyNameMapping ) {
 
-						header.format = lineValues[ 0 ];
-						header.version = lineValues[ 1 ];
+						property.name = propertyNameMapping[ property.name ];
 
-						break;
+					}
 
-					case 'comment':
+					return property;
 
-						header.comments.push( line );
+				}
 
-						break;
+				for ( var i = 0; i < lines.length; i ++ ) {
 
-					case 'element':
+					var line = lines[ i ];
+					line = line.trim();
+					if ( line === '' ) continue;
+					lineValues = line.split( /\s+/ );
+					lineType = lineValues.shift();
+					line = lineValues.join( ' ' );
 
-						if ( currentElement !== undefined ) {
+					switch ( lineType ) {
 
-							header.elements.push( currentElement );
+						case 'format':
+							header.format = lineValues[ 0 ];
+							header.version = lineValues[ 1 ];
+							break;
 
-						}
+						case 'comment':
+							header.comments.push( line );
+							break;
 
-						currentElement = {};
-						currentElement.name = lineValues[ 0 ];
-						currentElement.count = parseInt( lineValues[ 1 ] );
-						currentElement.properties = [];
+						case 'element':
+							if ( currentElement !== undefined ) {
 
-						break;
+								header.elements.push( currentElement );
 
-					case 'property':
+							}
 
-						currentElement.properties.push( make_ply_element_property( lineValues, scope.propertyNameMapping ) );
+							currentElement = {};
+							currentElement.name = lineValues[ 0 ];
+							currentElement.count = parseInt( lineValues[ 1 ] );
+							currentElement.properties = [];
+							break;
 
-						break;
+						case 'property':
+							currentElement.properties.push( make_ply_element_property( lineValues, scope.propertyNameMapping ) );
+							break;
 
-					case 'obj_info':
+						case 'obj_info':
+							header.objInfo = line;
+							break;
 
-						header.objInfo = line;
+						default:
+							console.log( 'unhandled', lineType, lineValues );
 
-						break;
+					}
 
+				}
 
-					default:
+				if ( currentElement !== undefined ) {
 
-						console.log( 'unhandled', lineType, lineValues );
+					header.elements.push( currentElement );
 
 				}
 
+				return header;
+
 			}
 
-			if ( currentElement !== undefined ) {
+			function parseASCIINumber( n, type ) {
+
+				switch ( type ) {
+
+					case 'char':
+					case 'uchar':
+					case 'short':
+					case 'ushort':
+					case 'int':
+					case 'uint':
+					case 'int8':
+					case 'uint8':
+					case 'int16':
+					case 'uint16':
+					case 'int32':
+					case 'uint32':
+						return parseInt( n );
+
+					case 'float':
+					case 'double':
+					case 'float32':
+					case 'float64':
+						return parseFloat( n );
 
-				header.elements.push( currentElement );
+				}
 
 			}
 
-			return header;
+			function parseASCIIElement( properties, line ) {
 
-		}
+				var values = line.split( /\s+/ );
+				var element = {};
 
-		function parseASCIINumber( n, type ) {
+				for ( var i = 0; i < properties.length; i ++ ) {
 
-			switch ( type ) {
+					if ( properties[ i ].type === 'list' ) {
 
-				case 'char': case 'uchar': case 'short': case 'ushort': case 'int': case 'uint':
-				case 'int8': case 'uint8': case 'int16': case 'uint16': case 'int32': case 'uint32':
+						var list = [];
+						var n = parseASCIINumber( values.shift(), properties[ i ].countType );
 
-					return parseInt( n );
+						for ( var j = 0; j < n; j ++ ) {
 
-				case 'float': case 'double': case 'float32': case 'float64':
+							list.push( parseASCIINumber( values.shift(), properties[ i ].itemType ) );
 
-					return parseFloat( n );
+						}
 
-			}
+						element[ properties[ i ].name ] = list;
 
-		}
+					} else {
 
-		function parseASCIIElement( properties, line ) {
+						element[ properties[ i ].name ] = parseASCIINumber( values.shift(), properties[ i ].type );
 
-			var values = line.split( /\s+/ );
+					}
 
-			var element = {};
+				}
 
-			for ( var i = 0; i < properties.length; i ++ ) {
+				return element;
 
-				if ( properties[ i ].type === 'list' ) {
+			}
 
-					var list = [];
-					var n = parseASCIINumber( values.shift(), properties[ i ].countType );
+			function parseASCII( data, header ) {
 
-					for ( var j = 0; j < n; j ++ ) {
+				// PLY ascii format specification, as per http://en.wikipedia.org/wiki/PLY_(file_format)
+				var buffer = {
+					indices: [],
+					vertices: [],
+					normals: [],
+					uvs: [],
+					faceVertexUvs: [],
+					colors: []
+				};
+				var result;
+				var patternBody = /end_header\s([\s\S]*)$/;
+				var body = '';
 
-						list.push( parseASCIINumber( values.shift(), properties[ i ].itemType ) );
+				if ( ( result = patternBody.exec( data ) ) !== null ) {
 
-					}
+					body = result[ 1 ];
 
-					element[ properties[ i ].name ] = list;
+				}
 
-				} else {
+				var lines = body.split( '\n' );
+				var currentElement = 0;
+				var currentElementCount = 0;
 
-					element[ properties[ i ].name ] = parseASCIINumber( values.shift(), properties[ i ].type );
+				for ( var i = 0; i < lines.length; i ++ ) {
 
-				}
+					var line = lines[ i ];
+					line = line.trim();
 
-			}
+					if ( line === '' ) {
 
-			return element;
+						continue;
 
-		}
+					}
 
-		function parseASCII( data, header ) {
+					if ( currentElementCount >= header.elements[ currentElement ].count ) {
 
-			// PLY ascii format specification, as per http://en.wikipedia.org/wiki/PLY_(file_format)
+						currentElement ++;
+						currentElementCount = 0;
 
-			var buffer = {
-				indices: [],
-				vertices: [],
-				normals: [],
-				uvs: [],
-				faceVertexUvs: [],
-				colors: []
-			};
+					}
 
-			var result;
+					var element = parseASCIIElement( header.elements[ currentElement ].properties, line );
+					handleElement( buffer, header.elements[ currentElement ].name, element );
+					currentElementCount ++;
 
-			var patternBody = /end_header\s([\s\S]*)$/;
-			var body = '';
-			if ( ( result = patternBody.exec( data ) ) !== null ) {
+				}
 
-				body = result[ 1 ];
+				return postProcess( buffer );
 
 			}
 
-			var lines = body.split( '\n' );
-			var currentElement = 0;
-			var currentElementCount = 0;
+			function postProcess( buffer ) {
 
-			for ( var i = 0; i < lines.length; i ++ ) {
+				var geometry = new THREE.BufferGeometry(); // mandatory buffer data
 
-				var line = lines[ i ];
-				line = line.trim();
-				if ( line === '' ) {
+				if ( buffer.indices.length > 0 ) {
 
-					continue;
+					geometry.setIndex( buffer.indices );
 
 				}
 
-				if ( currentElementCount >= header.elements[ currentElement ].count ) {
+				geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( buffer.vertices, 3 ) ); // optional buffer data
 
-					currentElement ++;
-					currentElementCount = 0;
+				if ( buffer.normals.length > 0 ) {
 
-				}
+					geometry.setAttribute( 'normal', new THREE.Float32BufferAttribute( buffer.normals, 3 ) );
 
-				var element = parseASCIIElement( header.elements[ currentElement ].properties, line );
+				}
 
-				handleElement( buffer, header.elements[ currentElement ].name, element );
+				if ( buffer.uvs.length > 0 ) {
 
-				currentElementCount ++;
+					geometry.setAttribute( 'uv', new THREE.Float32BufferAttribute( buffer.uvs, 2 ) );
 
-			}
+				}
 
-			return postProcess( buffer );
+				if ( buffer.colors.length > 0 ) {
 
-		}
+					geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( buffer.colors, 3 ) );
 
-		function postProcess( buffer ) {
+				}
 
-			var geometry = new THREE.BufferGeometry();
+				if ( buffer.faceVertexUvs.length > 0 ) {
 
-			// mandatory buffer data
+					geometry = geometry.toNonIndexed();
+					geometry.setAttribute( 'uv', new THREE.Float32BufferAttribute( buffer.faceVertexUvs, 2 ) );
 
-			if ( buffer.indices.length > 0 ) {
+				}
 
-				geometry.setIndex( buffer.indices );
+				geometry.computeBoundingSphere();
+				return geometry;
 
 			}
 
-			geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( buffer.vertices, 3 ) );
-
-			// optional buffer data
-
-			if ( buffer.normals.length > 0 ) {
+			function handleElement( buffer, elementName, element ) {
 
-				geometry.setAttribute( 'normal', new THREE.Float32BufferAttribute( buffer.normals, 3 ) );
+				if ( elementName === 'vertex' ) {
 
-			}
+					buffer.vertices.push( element.x, element.y, element.z );
 
-			if ( buffer.uvs.length > 0 ) {
+					if ( 'nx' in element && 'ny' in element && 'nz' in element ) {
 
-				geometry.setAttribute( 'uv', new THREE.Float32BufferAttribute( buffer.uvs, 2 ) );
+						buffer.normals.push( element.nx, element.ny, element.nz );
 
-			}
+					}
 
-			if ( buffer.colors.length > 0 ) {
+					if ( 's' in element && 't' in element ) {
 
-				geometry.setAttribute( 'color', new THREE.Float32BufferAttribute( buffer.colors, 3 ) );
+						buffer.uvs.push( element.s, element.t );
 
-			}
+					}
 
-			if ( buffer.faceVertexUvs.length > 0 ) {
+					if ( 'red' in element && 'green' in element && 'blue' in element ) {
 
-				geometry = geometry.toNonIndexed();
-				geometry.setAttribute( 'uv', new THREE.Float32BufferAttribute( buffer.faceVertexUvs, 2 ) );
+						buffer.colors.push( element.red / 255.0, element.green / 255.0, element.blue / 255.0 );
 
-			}
+					}
 
-			geometry.computeBoundingSphere();
+				} else if ( elementName === 'face' ) {
 
-			return geometry;
+					var vertex_indices = element.vertex_indices || element.vertex_index; // issue #9338
 
-		}
+					var texcoord = element.texcoord;
 
-		function handleElement( buffer, elementName, element ) {
+					if ( vertex_indices.length === 3 ) {
 
-			if ( elementName === 'vertex' ) {
+						buffer.indices.push( vertex_indices[ 0 ], vertex_indices[ 1 ], vertex_indices[ 2 ] );
 
-				buffer.vertices.push( element.x, element.y, element.z );
+						if ( texcoord && texcoord.length === 6 ) {
 
-				if ( 'nx' in element && 'ny' in element && 'nz' in element ) {
+							buffer.faceVertexUvs.push( texcoord[ 0 ], texcoord[ 1 ] );
+							buffer.faceVertexUvs.push( texcoord[ 2 ], texcoord[ 3 ] );
+							buffer.faceVertexUvs.push( texcoord[ 4 ], texcoord[ 5 ] );
 
-					buffer.normals.push( element.nx, element.ny, element.nz );
+						}
 
-				}
+					} else if ( vertex_indices.length === 4 ) {
 
-				if ( 's' in element && 't' in element ) {
+						buffer.indices.push( vertex_indices[ 0 ], vertex_indices[ 1 ], vertex_indices[ 3 ] );
+						buffer.indices.push( vertex_indices[ 1 ], vertex_indices[ 2 ], vertex_indices[ 3 ] );
 
-					buffer.uvs.push( element.s, element.t );
+					}
 
 				}
 
-				if ( 'red' in element && 'green' in element && 'blue' in element ) {
-
-					buffer.colors.push( element.red / 255.0, element.green / 255.0, element.blue / 255.0 );
+			}
 
-				}
+			function binaryRead( dataview, at, type, little_endian ) {
 
-			} else if ( elementName === 'face' ) {
+				switch ( type ) {
 
-				var vertex_indices = element.vertex_indices || element.vertex_index; // issue #9338
-				var texcoord = element.texcoord;
+					// corespondences for non-specific length types here match rply:
+					case 'int8':
+					case 'char':
+						return [ dataview.getInt8( at ), 1 ];
 
-				if ( vertex_indices.length === 3 ) {
+					case 'uint8':
+					case 'uchar':
+						return [ dataview.getUint8( at ), 1 ];
 
-					buffer.indices.push( vertex_indices[ 0 ], vertex_indices[ 1 ], vertex_indices[ 2 ] );
+					case 'int16':
+					case 'short':
+						return [ dataview.getInt16( at, little_endian ), 2 ];
 
-					if ( texcoord && texcoord.length === 6 ) {
+					case 'uint16':
+					case 'ushort':
+						return [ dataview.getUint16( at, little_endian ), 2 ];
 
-						buffer.faceVertexUvs.push( texcoord[ 0 ], texcoord[ 1 ] );
-						buffer.faceVertexUvs.push( texcoord[ 2 ], texcoord[ 3 ] );
-						buffer.faceVertexUvs.push( texcoord[ 4 ], texcoord[ 5 ] );
+					case 'int32':
+					case 'int':
+						return [ dataview.getInt32( at, little_endian ), 4 ];
 
-					}
+					case 'uint32':
+					case 'uint':
+						return [ dataview.getUint32( at, little_endian ), 4 ];
 
-				} else if ( vertex_indices.length === 4 ) {
+					case 'float32':
+					case 'float':
+						return [ dataview.getFloat32( at, little_endian ), 4 ];
 
-					buffer.indices.push( vertex_indices[ 0 ], vertex_indices[ 1 ], vertex_indices[ 3 ] );
-					buffer.indices.push( vertex_indices[ 1 ], vertex_indices[ 2 ], vertex_indices[ 3 ] );
+					case 'float64':
+					case 'double':
+						return [ dataview.getFloat64( at, little_endian ), 8 ];
 
 				}
 
 			}
 
-		}
-
-		function binaryRead( dataview, at, type, little_endian ) {
+			function binaryReadElement( dataview, at, properties, little_endian ) {
 
-			switch ( type ) {
-
-				// corespondences for non-specific length types here match rply:
-				case 'int8':		case 'char':	 return [ dataview.getInt8( at ), 1 ];
-				case 'uint8':		case 'uchar':	 return [ dataview.getUint8( at ), 1 ];
-				case 'int16':		case 'short':	 return [ dataview.getInt16( at, little_endian ), 2 ];
-				case 'uint16':	case 'ushort': return [ dataview.getUint16( at, little_endian ), 2 ];
-				case 'int32':		case 'int':		 return [ dataview.getInt32( at, little_endian ), 4 ];
-				case 'uint32':	case 'uint':	 return [ dataview.getUint32( at, little_endian ), 4 ];
-				case 'float32': case 'float':	 return [ dataview.getFloat32( at, little_endian ), 4 ];
-				case 'float64': case 'double': return [ dataview.getFloat64( at, little_endian ), 8 ];
-
-			}
+				var element = {};
+				var result,
+					read = 0;
 
-		}
+				for ( var i = 0; i < properties.length; i ++ ) {
 
-		function binaryReadElement( dataview, at, properties, little_endian ) {
+					if ( properties[ i ].type === 'list' ) {
 
-			var element = {};
-			var result, read = 0;
+						var list = [];
+						result = binaryRead( dataview, at + read, properties[ i ].countType, little_endian );
+						var n = result[ 0 ];
+						read += result[ 1 ];
 
-			for ( var i = 0; i < properties.length; i ++ ) {
+						for ( var j = 0; j < n; j ++ ) {
 
-				if ( properties[ i ].type === 'list' ) {
+							result = binaryRead( dataview, at + read, properties[ i ].itemType, little_endian );
+							list.push( result[ 0 ] );
+							read += result[ 1 ];
 
-					var list = [];
+						}
 
-					result = binaryRead( dataview, at + read, properties[ i ].countType, little_endian );
-					var n = result[ 0 ];
-					read += result[ 1 ];
+						element[ properties[ i ].name ] = list;
 
-					for ( var j = 0; j < n; j ++ ) {
+					} else {
 
-						result = binaryRead( dataview, at + read, properties[ i ].itemType, little_endian );
-						list.push( result[ 0 ] );
+						result = binaryRead( dataview, at + read, properties[ i ].type, little_endian );
+						element[ properties[ i ].name ] = result[ 0 ];
 						read += result[ 1 ];
 
 					}
 
-					element[ properties[ i ].name ] = list;
-
-				} else {
-
-					result = binaryRead( dataview, at + read, properties[ i ].type, little_endian );
-					element[ properties[ i ].name ] = result[ 0 ];
-					read += result[ 1 ];
-
 				}
 
-			}
-
-			return [ element, read ];
-
-		}
+				return [ element, read ];
 
-		function parseBinary( data, header ) {
+			}
 
-			var buffer = {
-				indices: [],
-				vertices: [],
-				normals: [],
-				uvs: [],
-				faceVertexUvs: [],
-				colors: []
-			};
+			function parseBinary( data, header ) {
 
-			var little_endian = ( header.format === 'binary_little_endian' );
-			var body = new DataView( data, header.headerLength );
-			var result, loc = 0;
+				var buffer = {
+					indices: [],
+					vertices: [],
+					normals: [],
+					uvs: [],
+					faceVertexUvs: [],
+					colors: []
+				};
+				var little_endian = header.format === 'binary_little_endian';
+				var body = new DataView( data, header.headerLength );
+				var result,
+					loc = 0;
 
-			for ( var currentElement = 0; currentElement < header.elements.length; currentElement ++ ) {
+				for ( var currentElement = 0; currentElement < header.elements.length; currentElement ++ ) {
 
-				for ( var currentElementCount = 0; currentElementCount < header.elements[ currentElement ].count; currentElementCount ++ ) {
+					for ( var currentElementCount = 0; currentElementCount < header.elements[ currentElement ].count; currentElementCount ++ ) {
 
-					result = binaryReadElement( body, loc, header.elements[ currentElement ].properties, little_endian );
-					loc += result[ 1 ];
-					var element = result[ 0 ];
+						result = binaryReadElement( body, loc, header.elements[ currentElement ].properties, little_endian );
+						loc += result[ 1 ];
+						var element = result[ 0 ];
+						handleElement( buffer, header.elements[ currentElement ].name, element );
 
-					handleElement( buffer, header.elements[ currentElement ].name, element );
+					}
 
 				}
 
-			}
+				return postProcess( buffer );
 
-			return postProcess( buffer );
+			} //
 
-		}
 
-		//
+			var geometry;
+			var scope = this;
 
-		var geometry;
-		var scope = this;
+			if ( data instanceof ArrayBuffer ) {
 
-		if ( data instanceof ArrayBuffer ) {
+				var text = THREE.LoaderUtils.decodeText( new Uint8Array( data ) );
+				var header = parseHeader( text );
+				geometry = header.format === 'ascii' ? parseASCII( text, header ) : parseBinary( data, header );
 
-			var text = THREE.LoaderUtils.decodeText( new Uint8Array( data ) );
-			var header = parseHeader( text );
+			} else {
 
-			geometry = header.format === 'ascii' ? parseASCII( text, header ) : parseBinary( data, header );
+				geometry = parseASCII( data, parseHeader( data ) );
 
-		} else {
+			}
 
-			geometry = parseASCII( data, parseHeader( data ) );
+			return geometry;
 
 		}
+	} );
 
-		return geometry;
-
-	}
+	THREE.PLYLoader = PLYLoader;
 
-} );
+} )();

+ 167 - 198
examples/js/loaders/PRWMLoader.js

@@ -1,307 +1,276 @@
-/**
+( function () {
+
+	/**
  * See https://github.com/kchapelier/PRWM for more informations about this file format
  */
 
-THREE.PRWMLoader = ( function () {
+	var PRWMLoader = function () {
 
-	var bigEndianPlatform = null;
-
-	/**
+		var bigEndianPlatform = null;
+		/**
 	 * Check if the endianness of the platform is big-endian (most significant bit first)
 	 * @returns {boolean} True if big-endian, false if little-endian
 	 */
-	function isBigEndianPlatform() {
-
-		if ( bigEndianPlatform === null ) {
-
-			var buffer = new ArrayBuffer( 2 ),
-				uint8Array = new Uint8Array( buffer ),
-				uint16Array = new Uint16Array( buffer );
-
-			uint8Array[ 0 ] = 0xAA; // set first byte
-			uint8Array[ 1 ] = 0xBB; // set second byte
-			bigEndianPlatform = ( uint16Array[ 0 ] === 0xAABB );
-
-		}
-
-		return bigEndianPlatform;
-
-	}
-
-	// match the values defined in the spec to the TypedArray types
-	var InvertedEncodingTypes = [
-		null,
-		Float32Array,
-		null,
-		Int8Array,
-		Int16Array,
-		null,
-		Int32Array,
-		Uint8Array,
-		Uint16Array,
-		null,
-		Uint32Array
-	];
-
-	// define the method to use on a DataView, corresponding the TypedArray type
-	var getMethods = {
-		Uint16Array: 'getUint16',
-		Uint32Array: 'getUint32',
-		Int16Array: 'getInt16',
-		Int32Array: 'getInt32',
-		Float32Array: 'getFloat32',
-		Float64Array: 'getFloat64'
-	};
 
+		function isBigEndianPlatform() {
 
-	function copyFromBuffer( sourceArrayBuffer, viewType, position, length, fromBigEndian ) {
+			if ( bigEndianPlatform === null ) {
 
-		var bytesPerElement = viewType.BYTES_PER_ELEMENT,
-			result;
+				var buffer = new ArrayBuffer( 2 ),
+					uint8Array = new Uint8Array( buffer ),
+					uint16Array = new Uint16Array( buffer );
+				uint8Array[ 0 ] = 0xAA; // set first byte
 
-		if ( fromBigEndian === isBigEndianPlatform() || bytesPerElement === 1 ) {
+				uint8Array[ 1 ] = 0xBB; // set second byte
 
-			result = new viewType( sourceArrayBuffer, position, length );
-
-		} else {
-
-			var readView = new DataView( sourceArrayBuffer, position, length * bytesPerElement ),
-				getMethod = getMethods[ viewType.name ],
-				littleEndian = ! fromBigEndian,
-				i = 0;
-
-			result = new viewType( length );
-
-			for ( ; i < length; i ++ ) {
-
-				result[ i ] = readView[ getMethod ]( i * bytesPerElement, littleEndian );
+				bigEndianPlatform = uint16Array[ 0 ] === 0xAABB;
 
 			}
 
-		}
+			return bigEndianPlatform;
 
-		return result;
+		} // match the values defined in the spec to the TypedArray types
 
-	}
 
+		var InvertedEncodingTypes = [ null, Float32Array, null, Int8Array, Int16Array, null, Int32Array, Uint8Array, Uint16Array, null, Uint32Array ]; // define the method to use on a DataView, corresponding the TypedArray type
 
-	function decodePrwm( buffer ) {
+		var getMethods = {
+			Uint16Array: 'getUint16',
+			Uint32Array: 'getUint32',
+			Int16Array: 'getInt16',
+			Int32Array: 'getInt32',
+			Float32Array: 'getFloat32',
+			Float64Array: 'getFloat64'
+		};
 
-		var array = new Uint8Array( buffer ),
-			version = array[ 0 ],
-			flags = array[ 1 ],
-			indexedGeometry = !! ( flags >> 7 & 0x01 ),
-			indicesType = flags >> 6 & 0x01,
-			bigEndian = ( flags >> 5 & 0x01 ) === 1,
-			attributesNumber = flags & 0x1F,
-			valuesNumber = 0,
-			indicesNumber = 0;
+		function copyFromBuffer( sourceArrayBuffer, viewType, position, length, fromBigEndian ) {
 
-		if ( bigEndian ) {
+			var bytesPerElement = viewType.BYTES_PER_ELEMENT,
+				result;
 
-			valuesNumber = ( array[ 2 ] << 16 ) + ( array[ 3 ] << 8 ) + array[ 4 ];
-			indicesNumber = ( array[ 5 ] << 16 ) + ( array[ 6 ] << 8 ) + array[ 7 ];
+			if ( fromBigEndian === isBigEndianPlatform() || bytesPerElement === 1 ) {
 
-		} else {
+				result = new viewType( sourceArrayBuffer, position, length );
 
-			valuesNumber = array[ 2 ] + ( array[ 3 ] << 8 ) + ( array[ 4 ] << 16 );
-			indicesNumber = array[ 5 ] + ( array[ 6 ] << 8 ) + ( array[ 7 ] << 16 );
+			} else {
 
-		}
+				var readView = new DataView( sourceArrayBuffer, position, length * bytesPerElement ),
+					getMethod = getMethods[ viewType.name ],
+					littleEndian = ! fromBigEndian,
+					i = 0;
+				result = new viewType( length );
 
-		/** PRELIMINARY CHECKS **/
+				for ( ; i < length; i ++ ) {
 
-		if ( version === 0 ) {
+					result[ i ] = readView[ getMethod ]( i * bytesPerElement, littleEndian );
 
-			throw new Error( 'PRWM decoder: Invalid format version: 0' );
+				}
 
-		} else if ( version !== 1 ) {
+			}
 
-			throw new Error( 'PRWM decoder: Unsupported format version: ' + version );
+			return result;
 
 		}
 
-		if ( ! indexedGeometry ) {
+		function decodePrwm( buffer ) {
 
-			if ( indicesType !== 0 ) {
+			var array = new Uint8Array( buffer ),
+				version = array[ 0 ],
+				flags = array[ 1 ],
+				indexedGeometry = !! ( flags >> 7 & 0x01 ),
+				indicesType = flags >> 6 & 0x01,
+				bigEndian = ( flags >> 5 & 0x01 ) === 1,
+				attributesNumber = flags & 0x1F,
+				valuesNumber = 0,
+				indicesNumber = 0;
 
-				throw new Error( 'PRWM decoder: Indices type must be set to 0 for non-indexed geometries' );
+			if ( bigEndian ) {
 
-			} else if ( indicesNumber !== 0 ) {
+				valuesNumber = ( array[ 2 ] << 16 ) + ( array[ 3 ] << 8 ) + array[ 4 ];
+				indicesNumber = ( array[ 5 ] << 16 ) + ( array[ 6 ] << 8 ) + array[ 7 ];
 
-				throw new Error( 'PRWM decoder: Number of indices must be set to 0 for non-indexed geometries' );
+			} else {
 
-			}
+				valuesNumber = array[ 2 ] + ( array[ 3 ] << 8 ) + ( array[ 4 ] << 16 );
+				indicesNumber = array[ 5 ] + ( array[ 6 ] << 8 ) + ( array[ 7 ] << 16 );
 
-		}
+			}
+			/** PRELIMINARY CHECKS **/
 
-		/** PARSING **/
 
-		var pos = 8;
+			if ( version === 0 ) {
 
-		var attributes = {},
-			attributeName,
-			char,
-			attributeType,
-			cardinality,
-			encodingType,
-			arrayType,
-			values,
-			indices,
-			i;
+				throw new Error( 'PRWM decoder: Invalid format version: 0' );
 
-		for ( i = 0; i < attributesNumber; i ++ ) {
+			} else if ( version !== 1 ) {
 
-			attributeName = '';
+				throw new Error( 'PRWM decoder: Unsupported format version: ' + version );
 
-			while ( pos < array.length ) {
+			}
 
-				char = array[ pos ];
-				pos ++;
+			if ( ! indexedGeometry ) {
 
-				if ( char === 0 ) {
+				if ( indicesType !== 0 ) {
 
-					break;
+					throw new Error( 'PRWM decoder: Indices type must be set to 0 for non-indexed geometries' );
 
-				} else {
+				} else if ( indicesNumber !== 0 ) {
 
-					attributeName += String.fromCharCode( char );
+					throw new Error( 'PRWM decoder: Number of indices must be set to 0 for non-indexed geometries' );
 
 				}
 
 			}
+			/** PARSING **/
+
+
+			var pos = 8;
+			var attributes = {},
+				attributeName,
+				char,
+				attributeType,
+				cardinality,
+				encodingType,
+				arrayType,
+				values,
+				indices,
+				i;
 
-			flags = array[ pos ];
+			for ( i = 0; i < attributesNumber; i ++ ) {
 
-			attributeType = flags >> 7 & 0x01;
-			cardinality = ( flags >> 4 & 0x03 ) + 1;
-			encodingType = flags & 0x0F;
-			arrayType = InvertedEncodingTypes[ encodingType ];
+				attributeName = '';
 
-			pos ++;
+				while ( pos < array.length ) {
 
-			// padding to next multiple of 4
-			pos = Math.ceil( pos / 4 ) * 4;
+					char = array[ pos ];
+					pos ++;
 
-			values = copyFromBuffer( buffer, arrayType, pos, cardinality * valuesNumber, bigEndian );
+					if ( char === 0 ) {
 
-			pos += arrayType.BYTES_PER_ELEMENT * cardinality * valuesNumber;
+						break;
 
-			attributes[ attributeName ] = {
-				type: attributeType,
-				cardinality: cardinality,
-				values: values
-			};
+					} else {
 
-		}
+						attributeName += String.fromCharCode( char );
 
-		pos = Math.ceil( pos / 4 ) * 4;
+					}
 
-		indices = null;
+				}
 
-		if ( indexedGeometry ) {
+				flags = array[ pos ];
+				attributeType = flags >> 7 & 0x01;
+				cardinality = ( flags >> 4 & 0x03 ) + 1;
+				encodingType = flags & 0x0F;
+				arrayType = InvertedEncodingTypes[ encodingType ];
+				pos ++; // padding to next multiple of 4
+
+				pos = Math.ceil( pos / 4 ) * 4;
+				values = copyFromBuffer( buffer, arrayType, pos, cardinality * valuesNumber, bigEndian );
+				pos += arrayType.BYTES_PER_ELEMENT * cardinality * valuesNumber;
+				attributes[ attributeName ] = {
+					type: attributeType,
+					cardinality: cardinality,
+					values: values
+				};
 
-			indices = copyFromBuffer(
-				buffer,
-				indicesType === 1 ? Uint32Array : Uint16Array,
-				pos,
-				indicesNumber,
-				bigEndian
-			);
+			}
 
-		}
+			pos = Math.ceil( pos / 4 ) * 4;
+			indices = null;
 
-		return {
-			version: version,
-			attributes: attributes,
-			indices: indices
-		};
+			if ( indexedGeometry ) {
 
-	}
+				indices = copyFromBuffer( buffer, indicesType === 1 ? Uint32Array : Uint16Array, pos, indicesNumber, bigEndian );
 
-	// Define the public interface
+			}
 
-	function PRWMLoader( manager ) {
+			return {
+				version: version,
+				attributes: attributes,
+				indices: indices
+			};
 
-		THREE.Loader.call( this, manager );
+		} // Define the public interface
 
-	}
 
-	PRWMLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+		function PRWMLoader( manager ) {
 
-		constructor: PRWMLoader,
+			THREE.Loader.call( this, manager );
 
-		load: function ( url, onLoad, onProgress, onError ) {
+		}
 
-			var scope = this;
+		PRWMLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+			constructor: PRWMLoader,
+			load: function ( url, onLoad, onProgress, onError ) {
 
-			var loader = new THREE.FileLoader( scope.manager );
-			loader.setPath( scope.path );
-			loader.setResponseType( 'arraybuffer' );
-			loader.setRequestHeader( scope.requestHeader );
-			loader.setWithCredentials( scope.withCredentials );
+				var scope = this;
+				var loader = new THREE.FileLoader( scope.manager );
+				loader.setPath( scope.path );
+				loader.setResponseType( 'arraybuffer' );
+				loader.setRequestHeader( scope.requestHeader );
+				loader.setWithCredentials( scope.withCredentials );
+				url = url.replace( /\*/g, isBigEndianPlatform() ? 'be' : 'le' );
+				loader.load( url, function ( arrayBuffer ) {
 
-			url = url.replace( /\*/g, isBigEndianPlatform() ? 'be' : 'le' );
+					try {
 
-			loader.load( url, function ( arrayBuffer ) {
+						onLoad( scope.parse( arrayBuffer ) );
 
-				try {
+					} catch ( e ) {
 
-					onLoad( scope.parse( arrayBuffer ) );
+						if ( onError ) {
 
-				} catch ( e ) {
+							onError( e );
 
-					if ( onError ) {
+						} else {
 
-						onError( e );
+							console.error( e );
 
-					} else {
+						}
 
-						console.error( e );
+						scope.manager.itemError( url );
 
 					}
 
-					scope.manager.itemError( url );
+				}, onProgress, onError );
 
-				}
-
-			}, onProgress, onError );
+			},
+			parse: function ( arrayBuffer ) {
 
-		},
+				var data = decodePrwm( arrayBuffer ),
+					attributesKey = Object.keys( data.attributes ),
+					bufferGeometry = new THREE.BufferGeometry(),
+					attribute,
+					i;
 
-		parse: function ( arrayBuffer ) {
+				for ( i = 0; i < attributesKey.length; i ++ ) {
 
-			var data = decodePrwm( arrayBuffer ),
-				attributesKey = Object.keys( data.attributes ),
-				bufferGeometry = new THREE.BufferGeometry(),
-				attribute,
-				i;
+					attribute = data.attributes[ attributesKey[ i ] ];
+					bufferGeometry.setAttribute( attributesKey[ i ], new THREE.BufferAttribute( attribute.values, attribute.cardinality, attribute.normalized ) );
 
-			for ( i = 0; i < attributesKey.length; i ++ ) {
+				}
 
-				attribute = data.attributes[ attributesKey[ i ] ];
-				bufferGeometry.setAttribute( attributesKey[ i ], new THREE.BufferAttribute( attribute.values, attribute.cardinality, attribute.normalized ) );
+				if ( data.indices !== null ) {
 
-			}
+					bufferGeometry.setIndex( new THREE.BufferAttribute( data.indices, 1 ) );
 
-			if ( data.indices !== null ) {
+				}
 
-				bufferGeometry.setIndex( new THREE.BufferAttribute( data.indices, 1 ) );
+				return bufferGeometry;
 
 			}
+		} );
 
-			return bufferGeometry;
+		PRWMLoader.isBigEndianPlatform = function () {
 
-		}
-
-	} );
+			return isBigEndianPlatform();
 
-	PRWMLoader.isBigEndianPlatform = function () {
+		};
 
-		return isBigEndianPlatform();
+		return PRWMLoader;
 
-	};
+	}();
 
-	return PRWMLoader;
+	THREE.PRWMLoader = PRWMLoader;
 
 } )();

+ 177 - 193
examples/js/loaders/PVRLoader.js

@@ -1,242 +1,226 @@
-/*
+( function () {
+
+	/*
  *	 PVR v2 (legacy) parser
- *   TODO : Add Support for PVR v3 format
- *   TODO : implement loadMipmaps option
+ *	 TODO : Add Support for PVR v3 format
+ *	 TODO : implement loadMipmaps option
  */
 
-THREE.PVRLoader = function ( manager ) {
-
-	THREE.CompressedTextureLoader.call( this, manager );
-
-};
-
-THREE.PVRLoader.prototype = Object.assign( Object.create( THREE.CompressedTextureLoader.prototype ), {
+	var PVRLoader = function ( manager ) {
 
-	constructor: THREE.PVRLoader,
+		THREE.CompressedTextureLoader.call( this, manager );
 
-	parse: function ( buffer, loadMipmaps ) {
-
-		var headerLengthInt = 13;
-		var header = new Uint32Array( buffer, 0, headerLengthInt );
+	};
 
-		var pvrDatas = {
-			buffer: buffer,
-			header: header,
-			loadMipmaps: loadMipmaps
-		};
+	PVRLoader.prototype = Object.assign( Object.create( THREE.CompressedTextureLoader.prototype ), {
+		constructor: PVRLoader,
+		parse: function ( buffer, loadMipmaps ) {
 
-		if ( header[ 0 ] === 0x03525650 ) {
+			var headerLengthInt = 13;
+			var header = new Uint32Array( buffer, 0, headerLengthInt );
+			var pvrDatas = {
+				buffer: buffer,
+				header: header,
+				loadMipmaps: loadMipmaps
+			};
 
-			// PVR v3
+			if ( header[ 0 ] === 0x03525650 ) {
 
-			return THREE.PVRLoader._parseV3( pvrDatas );
+				// PVR v3
+				return PVRLoader._parseV3( pvrDatas );
 
-		} else if ( header[ 11 ] === 0x21525650 ) {
+			} else if ( header[ 11 ] === 0x21525650 ) {
 
-			// PVR v2
+				// PVR v2
+				return PVRLoader._parseV2( pvrDatas );
 
-			return THREE.PVRLoader._parseV2( pvrDatas );
+			} else {
 
-		} else {
+				console.error( 'THREE.PVRLoader: Unknown PVR format.' );
 
-			console.error( 'THREE.PVRLoader: Unknown PVR format.' );
+			}
 
 		}
+	} );
+
+	PVRLoader._parseV3 = function ( pvrDatas ) {
+
+		var header = pvrDatas.header;
+		var bpp, format;
+		var metaLen = header[ 12 ],
+			pixelFormat = header[ 2 ],
+			height = header[ 6 ],
+			width = header[ 7 ],
+			// numSurfs = header[ 9 ],
+			numFaces = header[ 10 ],
+			numMipmaps = header[ 11 ];
+
+		switch ( pixelFormat ) {
+
+			case 0:
+			// PVRTC 2bpp RGB
+				bpp = 2;
+				format = THREE.RGB_PVRTC_2BPPV1_Format;
+				break;
+
+			case 1:
+			// PVRTC 2bpp RGBA
+				bpp = 2;
+				format = THREE.RGBA_PVRTC_2BPPV1_Format;
+				break;
+
+			case 2:
+			// PVRTC 4bpp RGB
+				bpp = 4;
+				format = THREE.RGB_PVRTC_4BPPV1_Format;
+				break;
+
+			case 3:
+			// PVRTC 4bpp RGBA
+				bpp = 4;
+				format = THREE.RGBA_PVRTC_4BPPV1_Format;
+				break;
+
+			default:
+				console.error( 'THREE.PVRLoader: Unsupported PVR format:', pixelFormat );
 
-	}
-
-} );
-
-THREE.PVRLoader._parseV3 = function ( pvrDatas ) {
-
-	var header = pvrDatas.header;
-	var bpp, format;
-
-
-	var metaLen = header[ 12 ],
-		pixelFormat = header[ 2 ],
-		height = header[ 6 ],
-		width = header[ 7 ],
-		// numSurfs = header[ 9 ],
-		numFaces = header[ 10 ],
-		numMipmaps = header[ 11 ];
-
-	switch ( pixelFormat ) {
+		}
 
-		case 0 : // PVRTC 2bpp RGB
-			bpp = 2;
-			format = THREE.RGB_PVRTC_2BPPV1_Format;
-			break;
+		pvrDatas.dataPtr = 52 + metaLen;
+		pvrDatas.bpp = bpp;
+		pvrDatas.format = format;
+		pvrDatas.width = width;
+		pvrDatas.height = height;
+		pvrDatas.numSurfaces = numFaces;
+		pvrDatas.numMipmaps = numMipmaps;
+		pvrDatas.isCubemap = numFaces === 6;
+		return PVRLoader._extract( pvrDatas );
 
-		case 1 : // PVRTC 2bpp RGBA
-			bpp = 2;
-			format = THREE.RGBA_PVRTC_2BPPV1_Format;
-			break;
-
-		case 2 : // PVRTC 4bpp RGB
-			bpp = 4;
-			format = THREE.RGB_PVRTC_4BPPV1_Format;
-			break;
+	};
 
-		case 3 : // PVRTC 4bpp RGBA
+	PVRLoader._parseV2 = function ( pvrDatas ) {
+
+		var header = pvrDatas.header;
+		var headerLength = header[ 0 ],
+			height = header[ 1 ],
+			width = header[ 2 ],
+			numMipmaps = header[ 3 ],
+			flags = header[ 4 ],
+			// dataLength = header[ 5 ],
+			// bpp =	header[ 6 ],
+			// bitmaskRed = header[ 7 ],
+			// bitmaskGreen = header[ 8 ],
+			// bitmaskBlue = header[ 9 ],
+			bitmaskAlpha = header[ 10 ],
+			// pvrTag = header[ 11 ],
+			numSurfs = header[ 12 ];
+		var TYPE_MASK = 0xff;
+		var PVRTC_2 = 24,
+			PVRTC_4 = 25;
+		var formatFlags = flags & TYPE_MASK;
+		var bpp, format;
+
+		var _hasAlpha = bitmaskAlpha > 0;
+
+		if ( formatFlags === PVRTC_4 ) {
+
+			format = _hasAlpha ? THREE.RGBA_PVRTC_4BPPV1_Format : THREE.RGB_PVRTC_4BPPV1_Format;
 			bpp = 4;
-			format = THREE.RGBA_PVRTC_4BPPV1_Format;
-			break;
-
-		default :
-			console.error( 'THREE.PVRLoader: Unsupported PVR format:', pixelFormat );
-
-	}
-
-	pvrDatas.dataPtr = 52 + metaLen;
-	pvrDatas.bpp = bpp;
-	pvrDatas.format = format;
-	pvrDatas.width = width;
-	pvrDatas.height = height;
-	pvrDatas.numSurfaces = numFaces;
-	pvrDatas.numMipmaps = numMipmaps;
-	pvrDatas.isCubemap 	= ( numFaces === 6 );
-
-	return THREE.PVRLoader._extract( pvrDatas );
-
-};
-
-THREE.PVRLoader._parseV2 = function ( pvrDatas ) {
 
-	var header = pvrDatas.header;
+		} else if ( formatFlags === PVRTC_2 ) {
 
-	var headerLength = header[ 0 ],
-		height = header[ 1 ],
-		width = header[ 2 ],
-		numMipmaps = header[ 3 ],
-		flags = header[ 4 ],
-		// dataLength = header[ 5 ],
-		// bpp =  header[ 6 ],
-		// bitmaskRed = header[ 7 ],
-		// bitmaskGreen = header[ 8 ],
-		// bitmaskBlue = header[ 9 ],
-		bitmaskAlpha = header[ 10 ],
-		// pvrTag = header[ 11 ],
-		numSurfs = header[ 12 ];
-
-
-	var TYPE_MASK = 0xff;
-	var PVRTC_2 = 24,
-		PVRTC_4 = 25;
-
-	var formatFlags = flags & TYPE_MASK;
-
-	var bpp, format;
-	var _hasAlpha = bitmaskAlpha > 0;
-
-	if ( formatFlags === PVRTC_4 ) {
-
-		format = _hasAlpha ? THREE.RGBA_PVRTC_4BPPV1_Format : THREE.RGB_PVRTC_4BPPV1_Format;
-		bpp = 4;
-
-	} else if ( formatFlags === PVRTC_2 ) {
-
-		format = _hasAlpha ? THREE.RGBA_PVRTC_2BPPV1_Format : THREE.RGB_PVRTC_2BPPV1_Format;
-		bpp = 2;
-
-	} else {
-
-		console.error( 'THREE.PVRLoader: Unknown PVR format:', formatFlags );
-
-	}
-
-	pvrDatas.dataPtr = headerLength;
-	pvrDatas.bpp = bpp;
-	pvrDatas.format = format;
-	pvrDatas.width = width;
-	pvrDatas.height = height;
-	pvrDatas.numSurfaces = numSurfs;
-	pvrDatas.numMipmaps = numMipmaps + 1;
+			format = _hasAlpha ? THREE.RGBA_PVRTC_2BPPV1_Format : THREE.RGB_PVRTC_2BPPV1_Format;
+			bpp = 2;
 
-	// guess cubemap type seems tricky in v2
-	// it juste a pvr containing 6 surface (no explicit cubemap type)
-	pvrDatas.isCubemap 	= ( numSurfs === 6 );
+		} else {
 
-	return THREE.PVRLoader._extract( pvrDatas );
+			console.error( 'THREE.PVRLoader: Unknown PVR format:', formatFlags );
 
-};
+		}
 
+		pvrDatas.dataPtr = headerLength;
+		pvrDatas.bpp = bpp;
+		pvrDatas.format = format;
+		pvrDatas.width = width;
+		pvrDatas.height = height;
+		pvrDatas.numSurfaces = numSurfs;
+		pvrDatas.numMipmaps = numMipmaps + 1; // guess cubemap type seems tricky in v2
+		// it juste a pvr containing 6 surface (no explicit cubemap type)
 
-THREE.PVRLoader._extract = function ( pvrDatas ) {
+		pvrDatas.isCubemap = numSurfs === 6;
+		return PVRLoader._extract( pvrDatas );
 
-	var pvr = {
-		mipmaps: [],
-		width: pvrDatas.width,
-		height: pvrDatas.height,
-		format: pvrDatas.format,
-		mipmapCount: pvrDatas.numMipmaps,
-		isCubemap: pvrDatas.isCubemap
 	};
 
-	var buffer = pvrDatas.buffer;
-
-	var dataOffset = pvrDatas.dataPtr,
-		bpp = pvrDatas.bpp,
-		numSurfs = pvrDatas.numSurfaces,
-		dataSize = 0,
-		blockSize = 0,
-		blockWidth = 0,
-		blockHeight = 0,
-		widthBlocks = 0,
-		heightBlocks = 0;
-
-	if ( bpp === 2 ) {
-
-		blockWidth = 8;
-		blockHeight = 4;
-
-	} else {
-
-		blockWidth = 4;
-		blockHeight = 4;
-
-	}
+	PVRLoader._extract = function ( pvrDatas ) {
 
-	blockSize = ( blockWidth * blockHeight ) * bpp / 8;
-
-	pvr.mipmaps.length = pvrDatas.numMipmaps * numSurfs;
+		var pvr = {
+			mipmaps: [],
+			width: pvrDatas.width,
+			height: pvrDatas.height,
+			format: pvrDatas.format,
+			mipmapCount: pvrDatas.numMipmaps,
+			isCubemap: pvrDatas.isCubemap
+		};
+		var buffer = pvrDatas.buffer;
+		var dataOffset = pvrDatas.dataPtr,
+			bpp = pvrDatas.bpp,
+			numSurfs = pvrDatas.numSurfaces,
+			dataSize = 0,
+			blockSize = 0,
+			blockWidth = 0,
+			blockHeight = 0,
+			widthBlocks = 0,
+			heightBlocks = 0;
+
+		if ( bpp === 2 ) {
+
+			blockWidth = 8;
+			blockHeight = 4;
 
-	var mipLevel = 0;
+		} else {
 
-	while ( mipLevel < pvrDatas.numMipmaps ) {
+			blockWidth = 4;
+			blockHeight = 4;
 
-		var sWidth = pvrDatas.width >> mipLevel,
-			sHeight = pvrDatas.height >> mipLevel;
+		}
 
-		widthBlocks = sWidth / blockWidth;
-		heightBlocks = sHeight / blockHeight;
+		blockSize = blockWidth * blockHeight * bpp / 8;
+		pvr.mipmaps.length = pvrDatas.numMipmaps * numSurfs;
+		var mipLevel = 0;
 
-		// Clamp to minimum number of blocks
-		if ( widthBlocks < 2 ) widthBlocks = 2;
-		if ( heightBlocks < 2 ) heightBlocks = 2;
+		while ( mipLevel < pvrDatas.numMipmaps ) {
 
-		dataSize = widthBlocks * heightBlocks * blockSize;
+			var sWidth = pvrDatas.width >> mipLevel,
+				sHeight = pvrDatas.height >> mipLevel;
+			widthBlocks = sWidth / blockWidth;
+			heightBlocks = sHeight / blockHeight; // Clamp to minimum number of blocks
 
-		for ( var surfIndex = 0; surfIndex < numSurfs; surfIndex ++ ) {
+			if ( widthBlocks < 2 ) widthBlocks = 2;
+			if ( heightBlocks < 2 ) heightBlocks = 2;
+			dataSize = widthBlocks * heightBlocks * blockSize;
 
-			var byteArray = new Uint8Array( buffer, dataOffset, dataSize );
+			for ( var surfIndex = 0; surfIndex < numSurfs; surfIndex ++ ) {
 
-			var mipmap = {
-				data: byteArray,
-				width: sWidth,
-				height: sHeight
-			};
+				var byteArray = new Uint8Array( buffer, dataOffset, dataSize );
+				var mipmap = {
+					data: byteArray,
+					width: sWidth,
+					height: sHeight
+				};
+				pvr.mipmaps[ surfIndex * pvrDatas.numMipmaps + mipLevel ] = mipmap;
+				dataOffset += dataSize;
 
-			pvr.mipmaps[ surfIndex * pvrDatas.numMipmaps + mipLevel ] = mipmap;
+			}
 
-			dataOffset += dataSize;
+			mipLevel ++;
 
 		}
 
-		mipLevel ++;
+		return pvr;
 
-	}
+	};
 
-	return pvr;
+	THREE.PVRLoader = PVRLoader;
 
-};
+} )();

+ 330 - 309
examples/js/loaders/RGBELoader.js

@@ -1,475 +1,496 @@
-// https://github.com/mrdoob/three.js/issues/5552
-// http://en.wikipedia.org/wiki/RGBE_image_format
+( function () {
 
-THREE.RGBELoader = function ( manager ) {
+	// http://en.wikipedia.org/wiki/RGBE_image_format
 
-	THREE.DataTextureLoader.call( this, manager );
+	var RGBELoader = function ( manager ) {
 
-	this.type = THREE.UnsignedByteType;
+		THREE.DataTextureLoader.call( this, manager );
+		this.type = THREE.UnsignedByteType;
 
-};
+	};
 
-THREE.RGBELoader.prototype = Object.assign( Object.create( THREE.DataTextureLoader.prototype ), {
+	RGBELoader.prototype = Object.assign( Object.create( THREE.DataTextureLoader.prototype ), {
+		constructor: RGBELoader,
+		// adapted from http://www.graphics.cornell.edu/~bjw/rgbe.html
+		parse: function ( buffer ) {
 
-	constructor: THREE.RGBELoader,
+			var
+				/* return codes for rgbe routines */
+				//RGBE_RETURN_SUCCESS = 0,
+				RGBE_RETURN_FAILURE = - 1,
 
-	// adapted from http://www.graphics.cornell.edu/~bjw/rgbe.html
+				/* default error routine.	change this to change error handling */
+				rgbe_read_error = 1,
+				rgbe_write_error = 2,
+				rgbe_format_error = 3,
+				rgbe_memory_error = 4,
+				rgbe_error = function ( rgbe_error_code, msg ) {
 
-	parse: function ( buffer ) {
+					switch ( rgbe_error_code ) {
 
-		var
-			/* return codes for rgbe routines */
-			//RGBE_RETURN_SUCCESS = 0,
-			RGBE_RETURN_FAILURE = - 1,
+						case rgbe_read_error:
+							console.error( 'THREE.RGBELoader Read Error: ' + ( msg || '' ) );
+							break;
 
-			/* default error routine.  change this to change error handling */
-			rgbe_read_error = 1,
-			rgbe_write_error = 2,
-			rgbe_format_error = 3,
-			rgbe_memory_error = 4,
-			rgbe_error = function ( rgbe_error_code, msg ) {
+						case rgbe_write_error:
+							console.error( 'THREE.RGBELoader Write Error: ' + ( msg || '' ) );
+							break;
 
-				switch ( rgbe_error_code ) {
+						case rgbe_format_error:
+							console.error( 'THREE.RGBELoader Bad File Format: ' + ( msg || '' ) );
+							break;
 
-					case rgbe_read_error: console.error( 'THREE.RGBELoader Read Error: ' + ( msg || '' ) );
-						break;
-					case rgbe_write_error: console.error( 'THREE.RGBELoader Write Error: ' + ( msg || '' ) );
-						break;
-					case rgbe_format_error: console.error( 'THREE.RGBELoader Bad File Format: ' + ( msg || '' ) );
-						break;
-					default:
-					case rgbe_memory_error: console.error( 'THREE.RGBELoader: Error: ' + ( msg || '' ) );
+						default:
+						case rgbe_memory_error:
+							console.error( 'THREE.RGBELoader: Error: ' + ( msg || '' ) );
 
-				}
-
-				return RGBE_RETURN_FAILURE;
+					}
 
-			},
+					return RGBE_RETURN_FAILURE;
 
-			/* offsets to red, green, and blue components in a data (float) pixel */
-			//RGBE_DATA_RED = 0,
-			//RGBE_DATA_GREEN = 1,
-			//RGBE_DATA_BLUE = 2,
+				},
 
-			/* number of floats per pixel, use 4 since stored in rgba image format */
-			//RGBE_DATA_SIZE = 4,
+				/* offsets to red, green, and blue components in a data (float) pixel */
+				//RGBE_DATA_RED = 0,
+				//RGBE_DATA_GREEN = 1,
+				//RGBE_DATA_BLUE = 2,
 
-			/* flags indicating which fields in an rgbe_header_info are valid */
-			RGBE_VALID_PROGRAMTYPE = 1,
-			RGBE_VALID_FORMAT = 2,
-			RGBE_VALID_DIMENSIONS = 4,
+				/* number of floats per pixel, use 4 since stored in rgba image format */
+				//RGBE_DATA_SIZE = 4,
 
-			NEWLINE = '\n',
+				/* flags indicating which fields in an rgbe_header_info are valid */
+				RGBE_VALID_PROGRAMTYPE = 1,
+				RGBE_VALID_FORMAT = 2,
+				RGBE_VALID_DIMENSIONS = 4,
+				NEWLINE = '\n',
+				fgets = function ( buffer, lineLimit, consume ) {
 
-			fgets = function ( buffer, lineLimit, consume ) {
+					lineLimit = ! lineLimit ? 1024 : lineLimit;
+					var p = buffer.pos,
+						i = - 1,
+						len = 0,
+						s = '',
+						chunkSize = 128,
+						chunk = String.fromCharCode.apply( null, new Uint16Array( buffer.subarray( p, p + chunkSize ) ) );
 
-				lineLimit = ! lineLimit ? 1024 : lineLimit;
-				var p = buffer.pos,
-					i = - 1, len = 0, s = '', chunkSize = 128,
-					chunk = String.fromCharCode.apply( null, new Uint16Array( buffer.subarray( p, p + chunkSize ) ) )
-				;
-				while ( ( 0 > ( i = chunk.indexOf( NEWLINE ) ) ) && ( len < lineLimit ) && ( p < buffer.byteLength ) ) {
+					while ( 0 > ( i = chunk.indexOf( NEWLINE ) ) && len < lineLimit && p < buffer.byteLength ) {
 
-					s += chunk; len += chunk.length;
-					p += chunkSize;
-					chunk += String.fromCharCode.apply( null, new Uint16Array( buffer.subarray( p, p + chunkSize ) ) );
+						s += chunk;
+						len += chunk.length;
+						p += chunkSize;
+						chunk += String.fromCharCode.apply( null, new Uint16Array( buffer.subarray( p, p + chunkSize ) ) );
 
-				}
+					}
 
-				if ( - 1 < i ) {
+					if ( - 1 < i ) {
 
-					/*for (i=l-1; i>=0; i--) {
-						byteCode = m.charCodeAt(i);
-						if (byteCode > 0x7f && byteCode <= 0x7ff) byteLen++;
-						else if (byteCode > 0x7ff && byteCode <= 0xffff) byteLen += 2;
-						if (byteCode >= 0xDC00 && byteCode <= 0xDFFF) i--; //trail surrogate
-					}*/
-					if ( false !== consume ) buffer.pos += len + i + 1;
-					return s + chunk.slice( 0, i );
+						/*for (i=l-1; i>=0; i--) {
+					byteCode = m.charCodeAt(i);
+					if (byteCode > 0x7f && byteCode <= 0x7ff) byteLen++;
+					else if (byteCode > 0x7ff && byteCode <= 0xffff) byteLen += 2;
+					if (byteCode >= 0xDC00 && byteCode <= 0xDFFF) i--; //trail surrogate
+				}*/
+						if ( false !== consume ) buffer.pos += len + i + 1;
+						return s + chunk.slice( 0, i );
 
-				}
+					}
 
-				return false;
+					return false;
 
-			},
+				},
 
-			/* minimal header reading.  modify if you want to parse more information */
-			RGBE_ReadHeader = function ( buffer ) {
+				/* minimal header reading.	modify if you want to parse more information */
+				RGBE_ReadHeader = function ( buffer ) {
 
-				var line, match,
+					var line,
+						match,
+						// regexes to parse header info fields
+						magic_token_re = /^#\?(\S+)/,
+						gamma_re = /^\s*GAMMA\s*=\s*(\d+(\.\d+)?)\s*$/,
+						exposure_re = /^\s*EXPOSURE\s*=\s*(\d+(\.\d+)?)\s*$/,
+						format_re = /^\s*FORMAT=(\S+)\s*$/,
+						dimensions_re = /^\s*\-Y\s+(\d+)\s+\+X\s+(\d+)\s*$/,
+						// RGBE format header struct
+						header = {
+							valid: 0,
 
-					// regexes to parse header info fields
-					magic_token_re = /^#\?(\S+)/,
-					gamma_re = /^\s*GAMMA\s*=\s*(\d+(\.\d+)?)\s*$/,
-					exposure_re = /^\s*EXPOSURE\s*=\s*(\d+(\.\d+)?)\s*$/,
-					format_re = /^\s*FORMAT=(\S+)\s*$/,
-					dimensions_re = /^\s*\-Y\s+(\d+)\s+\+X\s+(\d+)\s*$/,
+							/* indicate which fields are valid */
+							string: '',
 
-					// RGBE format header struct
-					header = {
+							/* the actual header string */
+							comments: '',
 
-						valid: 0, /* indicate which fields are valid */
+							/* comments found in header */
+							programtype: 'RGBE',
 
-						string: '', /* the actual header string */
+							/* listed at beginning of file to identify it after "#?". defaults to "RGBE" */
+							format: '',
 
-						comments: '', /* comments found in header */
+							/* RGBE format, default 32-bit_rle_rgbe */
+							gamma: 1.0,
 
-						programtype: 'RGBE', /* listed at beginning of file to identify it after "#?". defaults to "RGBE" */
+							/* image has already been gamma corrected with given gamma. defaults to 1.0 (no correction) */
+							exposure: 1.0,
 
-						format: '', /* RGBE format, default 32-bit_rle_rgbe */
+							/* a value of 1.0 in an image corresponds to <exposure> watts/steradian/m^2. defaults to 1.0 */
+							width: 0,
+							height: 0
+							/* image dimensions, width/height */
 
-						gamma: 1.0, /* image has already been gamma corrected with given gamma. defaults to 1.0 (no correction) */
+						};
 
-						exposure: 1.0, /* a value of 1.0 in an image corresponds to <exposure> watts/steradian/m^2. defaults to 1.0 */
+					if ( buffer.pos >= buffer.byteLength || ! ( line = fgets( buffer ) ) ) {
 
-						width: 0, height: 0 /* image dimensions, width/height */
+						return rgbe_error( rgbe_read_error, 'no header found' );
 
-					};
+					}
+					/* if you want to require the magic token then uncomment the next line */
 
-				if ( buffer.pos >= buffer.byteLength || ! ( line = fgets( buffer ) ) ) {
 
-					return rgbe_error( rgbe_read_error, 'no header found' );
+					if ( ! ( match = line.match( magic_token_re ) ) ) {
 
-				}
+						return rgbe_error( rgbe_format_error, 'bad initial token' );
 
-				/* if you want to require the magic token then uncomment the next line */
-				if ( ! ( match = line.match( magic_token_re ) ) ) {
+					}
 
-					return rgbe_error( rgbe_format_error, 'bad initial token' );
+					header.valid |= RGBE_VALID_PROGRAMTYPE;
+					header.programtype = match[ 1 ];
+					header.string += line + '\n';
 
-				}
+					while ( true ) {
 
-				header.valid |= RGBE_VALID_PROGRAMTYPE;
-				header.programtype = match[ 1 ];
-				header.string += line + '\n';
+						line = fgets( buffer );
+						if ( false === line ) break;
+						header.string += line + '\n';
 
-				while ( true ) {
+						if ( '#' === line.charAt( 0 ) ) {
 
-					line = fgets( buffer );
-					if ( false === line ) break;
-					header.string += line + '\n';
+							header.comments += line + '\n';
+							continue; // comment line
 
-					if ( '#' === line.charAt( 0 ) ) {
+						}
 
-						header.comments += line + '\n';
-						continue; // comment line
+						if ( match = line.match( gamma_re ) ) {
 
-					}
+							header.gamma = parseFloat( match[ 1 ], 10 );
 
-					if ( match = line.match( gamma_re ) ) {
+						}
 
-						header.gamma = parseFloat( match[ 1 ], 10 );
+						if ( match = line.match( exposure_re ) ) {
 
-					}
+							header.exposure = parseFloat( match[ 1 ], 10 );
 
-					if ( match = line.match( exposure_re ) ) {
+						}
 
-						header.exposure = parseFloat( match[ 1 ], 10 );
+						if ( match = line.match( format_re ) ) {
 
-					}
+							header.valid |= RGBE_VALID_FORMAT;
+							header.format = match[ 1 ]; //'32-bit_rle_rgbe';
 
-					if ( match = line.match( format_re ) ) {
+						}
 
-						header.valid |= RGBE_VALID_FORMAT;
-						header.format = match[ 1 ];//'32-bit_rle_rgbe';
+						if ( match = line.match( dimensions_re ) ) {
 
-					}
+							header.valid |= RGBE_VALID_DIMENSIONS;
+							header.height = parseInt( match[ 1 ], 10 );
+							header.width = parseInt( match[ 2 ], 10 );
 
-					if ( match = line.match( dimensions_re ) ) {
+						}
 
-						header.valid |= RGBE_VALID_DIMENSIONS;
-						header.height = parseInt( match[ 1 ], 10 );
-						header.width = parseInt( match[ 2 ], 10 );
+						if ( header.valid & RGBE_VALID_FORMAT && header.valid & RGBE_VALID_DIMENSIONS ) break;
 
 					}
 
-					if ( ( header.valid & RGBE_VALID_FORMAT ) && ( header.valid & RGBE_VALID_DIMENSIONS ) ) break;
+					if ( ! ( header.valid & RGBE_VALID_FORMAT ) ) {
 
-				}
+						return rgbe_error( rgbe_format_error, 'missing format specifier' );
 
-				if ( ! ( header.valid & RGBE_VALID_FORMAT ) ) {
+					}
 
-					return rgbe_error( rgbe_format_error, 'missing format specifier' );
+					if ( ! ( header.valid & RGBE_VALID_DIMENSIONS ) ) {
 
-				}
+						return rgbe_error( rgbe_format_error, 'missing image size specifier' );
 
-				if ( ! ( header.valid & RGBE_VALID_DIMENSIONS ) ) {
+					}
 
-					return rgbe_error( rgbe_format_error, 'missing image size specifier' );
+					return header;
+
+				},
+				RGBE_ReadPixels_RLE = function ( buffer, w, h ) {
+
+					var data_rgba,
+						offset,
+						pos,
+						count,
+						byteValue,
+						scanline_buffer,
+						ptr,
+						ptr_end,
+						i,
+						l,
+						off,
+						isEncodedRun,
+						scanline_width = w,
+						num_scanlines = h,
+						rgbeStart;
+
+					if ( // run length encoding is not allowed so read flat
+						scanline_width < 8 || scanline_width > 0x7fff || // this file is not run length encoded
+			2 !== buffer[ 0 ] || 2 !== buffer[ 1 ] || buffer[ 2 ] & 0x80 ) {
+
+						// return the flat buffer
+						return new Uint8Array( buffer );
 
-				}
+					}
 
-				return header;
+					if ( scanline_width !== ( buffer[ 2 ] << 8 | buffer[ 3 ] ) ) {
 
-			},
+						return rgbe_error( rgbe_format_error, 'wrong scanline width' );
 
-			RGBE_ReadPixels_RLE = function ( buffer, w, h ) {
+					}
 
-				var data_rgba, offset, pos, count, byteValue,
-					scanline_buffer, ptr, ptr_end, i, l, off, isEncodedRun,
-					scanline_width = w, num_scanlines = h, rgbeStart
-				;
+					data_rgba = new Uint8Array( 4 * w * h );
 
-				if (
-					// run length encoding is not allowed so read flat
-					( ( scanline_width < 8 ) || ( scanline_width > 0x7fff ) ) ||
-					// this file is not run length encoded
-					( ( 2 !== buffer[ 0 ] ) || ( 2 !== buffer[ 1 ] ) || ( buffer[ 2 ] & 0x80 ) )
-				) {
+					if ( ! data_rgba.length ) {
 
-					// return the flat buffer
-					return new Uint8Array( buffer );
+						return rgbe_error( rgbe_memory_error, 'unable to allocate buffer space' );
 
-				}
+					}
 
-				if ( scanline_width !== ( ( buffer[ 2 ] << 8 ) | buffer[ 3 ] ) ) {
+					offset = 0;
+					pos = 0;
+					ptr_end = 4 * scanline_width;
+					rgbeStart = new Uint8Array( 4 );
+					scanline_buffer = new Uint8Array( ptr_end ); // read in each successive scanline
 
-					return rgbe_error( rgbe_format_error, 'wrong scanline width' );
+					while ( num_scanlines > 0 && pos < buffer.byteLength ) {
 
-				}
+						if ( pos + 4 > buffer.byteLength ) {
 
-				data_rgba = new Uint8Array( 4 * w * h );
+							return rgbe_error( rgbe_read_error );
 
-				if ( ! data_rgba.length ) {
+						}
 
-					return rgbe_error( rgbe_memory_error, 'unable to allocate buffer space' );
+						rgbeStart[ 0 ] = buffer[ pos ++ ];
+						rgbeStart[ 1 ] = buffer[ pos ++ ];
+						rgbeStart[ 2 ] = buffer[ pos ++ ];
+						rgbeStart[ 3 ] = buffer[ pos ++ ];
 
-				}
+						if ( 2 != rgbeStart[ 0 ] || 2 != rgbeStart[ 1 ] || ( rgbeStart[ 2 ] << 8 | rgbeStart[ 3 ] ) != scanline_width ) {
 
-				offset = 0; pos = 0; ptr_end = 4 * scanline_width;
-				rgbeStart = new Uint8Array( 4 );
-				scanline_buffer = new Uint8Array( ptr_end );
+							return rgbe_error( rgbe_format_error, 'bad rgbe scanline format' );
 
-				// read in each successive scanline
-				while ( ( num_scanlines > 0 ) && ( pos < buffer.byteLength ) ) {
+						} // read each of the four channels for the scanline into the buffer
+						// first red, then green, then blue, then exponent
 
-					if ( pos + 4 > buffer.byteLength ) {
 
-						return rgbe_error( rgbe_read_error );
+						ptr = 0;
 
-					}
+						while ( ptr < ptr_end && pos < buffer.byteLength ) {
 
-					rgbeStart[ 0 ] = buffer[ pos ++ ];
-					rgbeStart[ 1 ] = buffer[ pos ++ ];
-					rgbeStart[ 2 ] = buffer[ pos ++ ];
-					rgbeStart[ 3 ] = buffer[ pos ++ ];
+							count = buffer[ pos ++ ];
+							isEncodedRun = count > 128;
+							if ( isEncodedRun ) count -= 128;
 
-					if ( ( 2 != rgbeStart[ 0 ] ) || ( 2 != rgbeStart[ 1 ] ) || ( ( ( rgbeStart[ 2 ] << 8 ) | rgbeStart[ 3 ] ) != scanline_width ) ) {
+							if ( 0 === count || ptr + count > ptr_end ) {
 
-						return rgbe_error( rgbe_format_error, 'bad rgbe scanline format' );
+								return rgbe_error( rgbe_format_error, 'bad scanline data' );
 
-					}
+							}
 
-					// read each of the four channels for the scanline into the buffer
-					// first red, then green, then blue, then exponent
-					ptr = 0;
-					while ( ( ptr < ptr_end ) && ( pos < buffer.byteLength ) ) {
+							if ( isEncodedRun ) {
 
-						count = buffer[ pos ++ ];
-						isEncodedRun = count > 128;
-						if ( isEncodedRun ) count -= 128;
+								// a (encoded) run of the same value
+								byteValue = buffer[ pos ++ ];
 
-						if ( ( 0 === count ) || ( ptr + count > ptr_end ) ) {
+								for ( i = 0; i < count; i ++ ) {
 
-							return rgbe_error( rgbe_format_error, 'bad scanline data' );
+									scanline_buffer[ ptr ++ ] = byteValue;
 
-						}
+								} //ptr += count;
 
-						if ( isEncodedRun ) {
+							} else {
 
-							// a (encoded) run of the same value
-							byteValue = buffer[ pos ++ ];
-							for ( i = 0; i < count; i ++ ) {
-
-								scanline_buffer[ ptr ++ ] = byteValue;
+								// a literal-run
+								scanline_buffer.set( buffer.subarray( pos, pos + count ), ptr );
+								ptr += count;
+								pos += count;
 
 							}
-							//ptr += count;
-
-						} else {
 
-							// a literal-run
-							scanline_buffer.set( buffer.subarray( pos, pos + count ), ptr );
-							ptr += count; pos += count;
+						} // now convert data from buffer into rgba
+						// first red, then green, then blue, then exponent (alpha)
 
-						}
 
-					}
+						l = scanline_width; //scanline_buffer.byteLength;
 
+						for ( i = 0; i < l; i ++ ) {
 
-					// now convert data from buffer into rgba
-					// first red, then green, then blue, then exponent (alpha)
-					l = scanline_width; //scanline_buffer.byteLength;
-					for ( i = 0; i < l; i ++ ) {
+							off = 0;
+							data_rgba[ offset ] = scanline_buffer[ i + off ];
+							off += scanline_width; //1;
 
-						off = 0;
-						data_rgba[ offset ] = scanline_buffer[ i + off ];
-						off += scanline_width; //1;
-						data_rgba[ offset + 1 ] = scanline_buffer[ i + off ];
-						off += scanline_width; //1;
-						data_rgba[ offset + 2 ] = scanline_buffer[ i + off ];
-						off += scanline_width; //1;
-						data_rgba[ offset + 3 ] = scanline_buffer[ i + off ];
-						offset += 4;
+							data_rgba[ offset + 1 ] = scanline_buffer[ i + off ];
+							off += scanline_width; //1;
 
-					}
+							data_rgba[ offset + 2 ] = scanline_buffer[ i + off ];
+							off += scanline_width; //1;
 
-					num_scanlines --;
+							data_rgba[ offset + 3 ] = scanline_buffer[ i + off ];
+							offset += 4;
 
-				}
+						}
 
-				return data_rgba;
+						num_scanlines --;
 
-			};
+					}
 
-		var RGBEByteToRGBFloat = function ( sourceArray, sourceOffset, destArray, destOffset ) {
+					return data_rgba;
 
-			var e = sourceArray[ sourceOffset + 3 ];
-			var scale = Math.pow( 2.0, e - 128.0 ) / 255.0;
+				};
 
-			destArray[ destOffset + 0 ] = sourceArray[ sourceOffset + 0 ] * scale;
-			destArray[ destOffset + 1 ] = sourceArray[ sourceOffset + 1 ] * scale;
-			destArray[ destOffset + 2 ] = sourceArray[ sourceOffset + 2 ] * scale;
+			var RGBEByteToRGBFloat = function ( sourceArray, sourceOffset, destArray, destOffset ) {
 
-		};
+				var e = sourceArray[ sourceOffset + 3 ];
+				var scale = Math.pow( 2.0, e - 128.0 ) / 255.0;
+				destArray[ destOffset + 0 ] = sourceArray[ sourceOffset + 0 ] * scale;
+				destArray[ destOffset + 1 ] = sourceArray[ sourceOffset + 1 ] * scale;
+				destArray[ destOffset + 2 ] = sourceArray[ sourceOffset + 2 ] * scale;
 
-		var RGBEByteToRGBHalf = function ( sourceArray, sourceOffset, destArray, destOffset ) {
+			};
 
-			var e = sourceArray[ sourceOffset + 3 ];
-			var scale = Math.pow( 2.0, e - 128.0 ) / 255.0;
+			var RGBEByteToRGBHalf = function ( sourceArray, sourceOffset, destArray, destOffset ) {
 
-			destArray[ destOffset + 0 ] = THREE.DataUtils.toHalfFloat( sourceArray[ sourceOffset + 0 ] * scale );
-			destArray[ destOffset + 1 ] = THREE.DataUtils.toHalfFloat( sourceArray[ sourceOffset + 1 ] * scale );
-			destArray[ destOffset + 2 ] = THREE.DataUtils.toHalfFloat( sourceArray[ sourceOffset + 2 ] * scale );
+				var e = sourceArray[ sourceOffset + 3 ];
+				var scale = Math.pow( 2.0, e - 128.0 ) / 255.0;
+				destArray[ destOffset + 0 ] = THREE.DataUtils.toHalfFloat( sourceArray[ sourceOffset + 0 ] * scale );
+				destArray[ destOffset + 1 ] = THREE.DataUtils.toHalfFloat( sourceArray[ sourceOffset + 1 ] * scale );
+				destArray[ destOffset + 2 ] = THREE.DataUtils.toHalfFloat( sourceArray[ sourceOffset + 2 ] * scale );
 
-		};
+			};
 
-		var byteArray = new Uint8Array( buffer );
-		byteArray.pos = 0;
-		var rgbe_header_info = RGBE_ReadHeader( byteArray );
+			var byteArray = new Uint8Array( buffer );
+			byteArray.pos = 0;
+			var rgbe_header_info = RGBE_ReadHeader( byteArray );
 
-		if ( RGBE_RETURN_FAILURE !== rgbe_header_info ) {
+			if ( RGBE_RETURN_FAILURE !== rgbe_header_info ) {
 
-			var w = rgbe_header_info.width,
-				h = rgbe_header_info.height,
-				image_rgba_data = RGBE_ReadPixels_RLE( byteArray.subarray( byteArray.pos ), w, h );
+				var w = rgbe_header_info.width,
+					h = rgbe_header_info.height,
+					image_rgba_data = RGBE_ReadPixels_RLE( byteArray.subarray( byteArray.pos ), w, h );
 
-			if ( RGBE_RETURN_FAILURE !== image_rgba_data ) {
+				if ( RGBE_RETURN_FAILURE !== image_rgba_data ) {
 
-				switch ( this.type ) {
+					switch ( this.type ) {
 
-					case THREE.UnsignedByteType:
+						case THREE.UnsignedByteType:
+							var data = image_rgba_data;
+							var format = THREE.RGBEFormat; // handled as THREE.RGBAFormat in shaders
 
-						var data = image_rgba_data;
-						var format = THREE.RGBEFormat; // handled as THREE.RGBAFormat in shaders
-						var type = THREE.UnsignedByteType;
-						break;
+							var type = THREE.UnsignedByteType;
+							break;
 
-					case THREE.FloatType:
+						case THREE.FloatType:
+							var numElements = image_rgba_data.length / 4 * 3;
+							var floatArray = new Float32Array( numElements );
 
-						var numElements = ( image_rgba_data.length / 4 ) * 3;
-						var floatArray = new Float32Array( numElements );
+							for ( var j = 0; j < numElements; j ++ ) {
 
-						for ( var j = 0; j < numElements; j ++ ) {
+								RGBEByteToRGBFloat( image_rgba_data, j * 4, floatArray, j * 3 );
 
-							RGBEByteToRGBFloat( image_rgba_data, j * 4, floatArray, j * 3 );
+							}
 
-						}
+							var data = floatArray;
+							var format = THREE.RGBFormat;
+							var type = THREE.FloatType;
+							break;
 
-						var data = floatArray;
-						var format = THREE.RGBFormat;
-						var type = THREE.FloatType;
-						break;
+						case THREE.HalfFloatType:
+							var numElements = image_rgba_data.length / 4 * 3;
+							var halfArray = new Uint16Array( numElements );
 
-					case THREE.HalfFloatType:
+							for ( var j = 0; j < numElements; j ++ ) {
 
-						var numElements = ( image_rgba_data.length / 4 ) * 3;
-						var halfArray = new Uint16Array( numElements );
+								RGBEByteToRGBHalf( image_rgba_data, j * 4, halfArray, j * 3 );
 
-						for ( var j = 0; j < numElements; j ++ ) {
+							}
 
-							RGBEByteToRGBHalf( image_rgba_data, j * 4, halfArray, j * 3 );
+							var data = halfArray;
+							var format = THREE.RGBFormat;
+							var type = THREE.HalfFloatType;
+							break;
 
-						}
+						default:
+							console.error( 'THREE.RGBELoader: unsupported type: ', this.type );
+							break;
 
-						var data = halfArray;
-						var format = THREE.RGBFormat;
-						var type = THREE.HalfFloatType;
-						break;
-
-					default:
+					}
 
-						console.error( 'THREE.RGBELoader: unsupported type: ', this.type );
-						break;
+					return {
+						width: w,
+						height: h,
+						data: data,
+						header: rgbe_header_info.string,
+						gamma: rgbe_header_info.gamma,
+						exposure: rgbe_header_info.exposure,
+						format: format,
+						type: type
+					};
 
 				}
 
-				return {
-					width: w, height: h,
-					data: data,
-					header: rgbe_header_info.string,
-					gamma: rgbe_header_info.gamma,
-					exposure: rgbe_header_info.exposure,
-					format: format,
-					type: type
-				};
-
 			}
 
-		}
-
-		return null;
-
-	},
+			return null;
 
-	setDataType: function ( value ) {
+		},
+		setDataType: function ( value ) {
 
-		this.type = value;
-		return this;
+			this.type = value;
+			return this;
 
-	},
+		},
+		load: function ( url, onLoad, onProgress, onError ) {
 
-	load: function ( url, onLoad, onProgress, onError ) {
+			function onLoadCallback( texture, texData ) {
 
-		function onLoadCallback( texture, texData ) {
+				switch ( texture.type ) {
 
-			switch ( texture.type ) {
-
-				case THREE.UnsignedByteType:
-
-					texture.encoding = THREE.RGBEEncoding;
-					texture.minFilter = THREE.NearestFilter;
-					texture.magFilter = THREE.NearestFilter;
-					texture.generateMipmaps = false;
-					texture.flipY = true;
-					break;
+					case THREE.UnsignedByteType:
+						texture.encoding = THREE.RGBEEncoding;
+						texture.minFilter = THREE.NearestFilter;
+						texture.magFilter = THREE.NearestFilter;
+						texture.generateMipmaps = false;
+						texture.flipY = true;
+						break;
 
-				case THREE.FloatType:
+					case THREE.FloatType:
+						texture.encoding = THREE.LinearEncoding;
+						texture.minFilter = THREE.LinearFilter;
+						texture.magFilter = THREE.LinearFilter;
+						texture.generateMipmaps = false;
+						texture.flipY = true;
+						break;
 
-					texture.encoding = THREE.LinearEncoding;
-					texture.minFilter = THREE.LinearFilter;
-					texture.magFilter = THREE.LinearFilter;
-					texture.generateMipmaps = false;
-					texture.flipY = true;
-					break;
+					case THREE.HalfFloatType:
+						texture.encoding = THREE.LinearEncoding;
+						texture.minFilter = THREE.LinearFilter;
+						texture.magFilter = THREE.LinearFilter;
+						texture.generateMipmaps = false;
+						texture.flipY = true;
+						break;
 
-				case THREE.HalfFloatType:
+				}
 
-					texture.encoding = THREE.LinearEncoding;
-					texture.minFilter = THREE.LinearFilter;
-					texture.magFilter = THREE.LinearFilter;
-					texture.generateMipmaps = false;
-					texture.flipY = true;
-					break;
+				if ( onLoad ) onLoad( texture, texData );
 
 			}
 
-			if ( onLoad ) onLoad( texture, texData );
+			return THREE.DataTextureLoader.prototype.load.call( this, url, onLoadCallback, onProgress, onError );
 
 		}
+	} );
 
-		return THREE.DataTextureLoader.prototype.load.call( this, url, onLoadCallback, onProgress, onError );
-
-	}
+	THREE.RGBELoader = RGBELoader;
 
-} );
+} )();

+ 1369 - 0
examples/js/loaders/RGBMLoader.js

@@ -0,0 +1,1369 @@
+( function () {
+
+	class RGBMLoader extends THREE.DataTextureLoader {
+
+		loadCubemap( urls, onLoad, onProgress, onError ) {
+
+			const texture = new THREE.CubeTexture();
+			let loaded = 0;
+			const scope = this;
+
+			function loadTexture( i ) {
+
+				scope.load( urls[ i ], function ( image ) {
+
+					texture.images[ i ] = image;
+					loaded ++;
+
+					if ( loaded === 6 ) {
+
+						texture.needsUpdate = true;
+						if ( onLoad ) onLoad( texture );
+
+					}
+
+				}, undefined, onError );
+
+			}
+
+			for ( let i = 0; i < urls.length; ++ i ) {
+
+				loadTexture( i );
+
+			}
+
+			texture.encoding = THREE.RGBM7Encoding;
+			texture.format = THREE.RGBAFormat;
+			texture.minFilter = THREE.LinearFilter;
+			texture.generateMipmaps = false;
+			return texture;
+
+		}
+
+		parse( buffer ) {
+
+			const img = UPNG.decode( buffer );
+			const rgba = UPNG.toRGBA8( img )[ 0 ];
+			return {
+				width: img.width,
+				height: img.height,
+				data: new Uint8Array( rgba ),
+				format: THREE.RGBAFormat,
+				type: THREE.UnsignedByteType,
+				flipY: true,
+				encoding: THREE.RGBM7Encoding
+			};
+
+		}
+
+	} // from https://github.com/photopea/UPNG.js (MIT License)
+
+
+	var UPNG = {};
+
+	UPNG.toRGBA8 = function ( out ) {
+
+		var w = out.width,
+			h = out.height;
+		if ( out.tabs.acTL == null ) return [ UPNG.toRGBA8.decodeImage( out.data, w, h, out ).buffer ];
+		var frms = [];
+		if ( out.frames[ 0 ].data == null ) out.frames[ 0 ].data = out.data;
+		var len = w * h * 4,
+			img = new Uint8Array( len ),
+			empty = new Uint8Array( len ),
+			prev = new Uint8Array( len );
+
+		for ( var i = 0; i < out.frames.length; i ++ ) {
+
+			var frm = out.frames[ i ];
+			var fx = frm.rect.x,
+				fy = frm.rect.y,
+				fw = frm.rect.width,
+				fh = frm.rect.height;
+			var fdata = UPNG.toRGBA8.decodeImage( frm.data, fw, fh, out );
+			if ( i != 0 ) for ( var j = 0; j < len; j ++ ) prev[ j ] = img[ j ];
+			if ( frm.blend == 0 ) UPNG._copyTile( fdata, fw, fh, img, w, h, fx, fy, 0 ); else if ( frm.blend == 1 ) UPNG._copyTile( fdata, fw, fh, img, w, h, fx, fy, 1 );
+			frms.push( img.buffer.slice( 0 ) );
+
+			if ( frm.dispose == 0 ) {} else if ( frm.dispose == 1 ) UPNG._copyTile( empty, fw, fh, img, w, h, fx, fy, 0 ); else if ( frm.dispose == 2 ) for ( var j = 0; j < len; j ++ ) img[ j ] = prev[ j ];
+
+		}
+
+		return frms;
+
+	};
+
+	UPNG.toRGBA8.decodeImage = function ( data, w, h, out ) {
+
+		var area = w * h,
+			bpp = UPNG.decode._getBPP( out );
+
+		var bpl = Math.ceil( w * bpp / 8 ); // bytes per line
+
+		var bf = new Uint8Array( area * 4 ),
+			bf32 = new Uint32Array( bf.buffer );
+		var ctype = out.ctype,
+			depth = out.depth;
+		var rs = UPNG._bin.readUshort;
+
+		if ( ctype == 6 ) {
+
+			// RGB + alpha
+			var qarea = area << 2;
+			if ( depth == 8 ) for ( var i = 0; i < qarea; i += 4 ) {
+
+				bf[ i ] = data[ i ];
+				bf[ i + 1 ] = data[ i + 1 ];
+				bf[ i + 2 ] = data[ i + 2 ];
+				bf[ i + 3 ] = data[ i + 3 ];
+
+			}
+
+			if ( depth == 16 ) for ( var i = 0; i < qarea; i ++ ) {
+
+				bf[ i ] = data[ i << 1 ];
+
+			}
+
+		} else if ( ctype == 2 ) {
+
+			// RGB
+			var ts = out.tabs[ 'tRNS' ];
+
+			if ( ts == null ) {
+
+				if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) {
+
+					var ti = i * 3;
+					bf32[ i ] = 255 << 24 | data[ ti + 2 ] << 16 | data[ ti + 1 ] << 8 | data[ ti ];
+
+				}
+
+				if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) {
+
+					var ti = i * 6;
+					bf32[ i ] = 255 << 24 | data[ ti + 4 ] << 16 | data[ ti + 2 ] << 8 | data[ ti ];
+
+				}
+
+			} else {
+
+				var tr = ts[ 0 ],
+					tg = ts[ 1 ],
+					tb = ts[ 2 ];
+				if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) {
+
+					var qi = i << 2,
+						ti = i * 3;
+					bf32[ i ] = 255 << 24 | data[ ti + 2 ] << 16 | data[ ti + 1 ] << 8 | data[ ti ];
+					if ( data[ ti ] == tr && data[ ti + 1 ] == tg && data[ ti + 2 ] == tb ) bf[ qi + 3 ] = 0;
+
+				}
+
+				if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) {
+
+					var qi = i << 2,
+						ti = i * 6;
+					bf32[ i ] = 255 << 24 | data[ ti + 4 ] << 16 | data[ ti + 2 ] << 8 | data[ ti ];
+					if ( rs( data, ti ) == tr && rs( data, ti + 2 ) == tg && rs( data, ti + 4 ) == tb ) bf[ qi + 3 ] = 0;
+
+				}
+
+			}
+
+		} else if ( ctype == 3 ) {
+
+			// palette
+			var p = out.tabs[ 'PLTE' ],
+				ap = out.tabs[ 'tRNS' ],
+				tl = ap ? ap.length : 0; //console.log(p, ap);
+
+			if ( depth == 1 ) for ( var y = 0; y < h; y ++ ) {
+
+				var s0 = y * bpl,
+					t0 = y * w;
+
+				for ( var i = 0; i < w; i ++ ) {
+
+					var qi = t0 + i << 2,
+						j = data[ s0 + ( i >> 3 ) ] >> 7 - ( ( i & 7 ) << 0 ) & 1,
+						cj = 3 * j;
+					bf[ qi ] = p[ cj ];
+					bf[ qi + 1 ] = p[ cj + 1 ];
+					bf[ qi + 2 ] = p[ cj + 2 ];
+					bf[ qi + 3 ] = j < tl ? ap[ j ] : 255;
+
+				}
+
+			}
+
+			if ( depth == 2 ) for ( var y = 0; y < h; y ++ ) {
+
+				var s0 = y * bpl,
+					t0 = y * w;
+
+				for ( var i = 0; i < w; i ++ ) {
+
+					var qi = t0 + i << 2,
+						j = data[ s0 + ( i >> 2 ) ] >> 6 - ( ( i & 3 ) << 1 ) & 3,
+						cj = 3 * j;
+					bf[ qi ] = p[ cj ];
+					bf[ qi + 1 ] = p[ cj + 1 ];
+					bf[ qi + 2 ] = p[ cj + 2 ];
+					bf[ qi + 3 ] = j < tl ? ap[ j ] : 255;
+
+				}
+
+			}
+
+			if ( depth == 4 ) for ( var y = 0; y < h; y ++ ) {
+
+				var s0 = y * bpl,
+					t0 = y * w;
+
+				for ( var i = 0; i < w; i ++ ) {
+
+					var qi = t0 + i << 2,
+						j = data[ s0 + ( i >> 1 ) ] >> 4 - ( ( i & 1 ) << 2 ) & 15,
+						cj = 3 * j;
+					bf[ qi ] = p[ cj ];
+					bf[ qi + 1 ] = p[ cj + 1 ];
+					bf[ qi + 2 ] = p[ cj + 2 ];
+					bf[ qi + 3 ] = j < tl ? ap[ j ] : 255;
+
+				}
+
+			}
+
+			if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) {
+
+				var qi = i << 2,
+					j = data[ i ],
+					cj = 3 * j;
+				bf[ qi ] = p[ cj ];
+				bf[ qi + 1 ] = p[ cj + 1 ];
+				bf[ qi + 2 ] = p[ cj + 2 ];
+				bf[ qi + 3 ] = j < tl ? ap[ j ] : 255;
+
+			}
+
+		} else if ( ctype == 4 ) {
+
+			// gray + alpha
+			if ( depth == 8 ) for ( var i = 0; i < area; i ++ ) {
+
+				var qi = i << 2,
+					di = i << 1,
+					gr = data[ di ];
+				bf[ qi ] = gr;
+				bf[ qi + 1 ] = gr;
+				bf[ qi + 2 ] = gr;
+				bf[ qi + 3 ] = data[ di + 1 ];
+
+			}
+
+			if ( depth == 16 ) for ( var i = 0; i < area; i ++ ) {
+
+				var qi = i << 2,
+					di = i << 2,
+					gr = data[ di ];
+				bf[ qi ] = gr;
+				bf[ qi + 1 ] = gr;
+				bf[ qi + 2 ] = gr;
+				bf[ qi + 3 ] = data[ di + 2 ];
+
+			}
+
+		} else if ( ctype == 0 ) {
+
+			// gray
+			var tr = out.tabs[ 'tRNS' ] ? out.tabs[ 'tRNS' ] : - 1;
+
+			for ( var y = 0; y < h; y ++ ) {
+
+				var off = y * bpl,
+					to = y * w;
+				if ( depth == 1 ) for ( var x = 0; x < w; x ++ ) {
+
+					var gr = 255 * ( data[ off + ( x >>> 3 ) ] >>> 7 - ( x & 7 ) & 1 ),
+						al = gr == tr * 255 ? 0 : 255;
+					bf32[ to + x ] = al << 24 | gr << 16 | gr << 8 | gr;
+
+				} else if ( depth == 2 ) for ( var x = 0; x < w; x ++ ) {
+
+					var gr = 85 * ( data[ off + ( x >>> 2 ) ] >>> 6 - ( ( x & 3 ) << 1 ) & 3 ),
+						al = gr == tr * 85 ? 0 : 255;
+					bf32[ to + x ] = al << 24 | gr << 16 | gr << 8 | gr;
+
+				} else if ( depth == 4 ) for ( var x = 0; x < w; x ++ ) {
+
+					var gr = 17 * ( data[ off + ( x >>> 1 ) ] >>> 4 - ( ( x & 1 ) << 2 ) & 15 ),
+						al = gr == tr * 17 ? 0 : 255;
+					bf32[ to + x ] = al << 24 | gr << 16 | gr << 8 | gr;
+
+				} else if ( depth == 8 ) for ( var x = 0; x < w; x ++ ) {
+
+					var gr = data[ off + x ],
+						al = gr == tr ? 0 : 255;
+					bf32[ to + x ] = al << 24 | gr << 16 | gr << 8 | gr;
+
+				} else if ( depth == 16 ) for ( var x = 0; x < w; x ++ ) {
+
+					var gr = data[ off + ( x << 1 ) ],
+						al = rs( data, off + ( x << i ) ) == tr ? 0 : 255;
+					bf32[ to + x ] = al << 24 | gr << 16 | gr << 8 | gr;
+
+				}
+
+			}
+
+		} //console.log(Date.now()-time);
+
+
+		return bf;
+
+	};
+
+	UPNG.decode = function ( buff ) {
+
+		var data = new Uint8Array( buff ),
+			offset = 8,
+			bin = UPNG._bin,
+			rUs = bin.readUshort,
+			rUi = bin.readUint;
+		var out = {
+			tabs: {},
+			frames: []
+		};
+		var dd = new Uint8Array( data.length ),
+			doff = 0; // put all IDAT data into it
+
+		var fd,
+			foff = 0; // frames
+
+		var mgck = [ 0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a ];
+
+		for ( var i = 0; i < 8; i ++ ) if ( data[ i ] != mgck[ i ] ) throw 'The input is not a PNG file!';
+
+		while ( offset < data.length ) {
+
+			var len = bin.readUint( data, offset );
+			offset += 4;
+			var type = bin.readASCII( data, offset, 4 );
+			offset += 4; //console.log(type,len);
+
+			if ( type == 'IHDR' ) {
+
+				UPNG.decode._IHDR( data, offset, out );
+
+			} else if ( type == 'CgBI' ) {
+
+				out.tabs[ type ] = data.slice( offset, offset + 4 );
+
+			} else if ( type == 'IDAT' ) {
+
+				for ( var i = 0; i < len; i ++ ) dd[ doff + i ] = data[ offset + i ];
+
+				doff += len;
+
+			} else if ( type == 'acTL' ) {
+
+				out.tabs[ type ] = {
+					num_frames: rUi( data, offset ),
+					num_plays: rUi( data, offset + 4 )
+				};
+				fd = new Uint8Array( data.length );
+
+			} else if ( type == 'fcTL' ) {
+
+				if ( foff != 0 ) {
+
+					var fr = out.frames[ out.frames.length - 1 ];
+					fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height );
+					foff = 0;
+
+				}
+
+				var rct = {
+					x: rUi( data, offset + 12 ),
+					y: rUi( data, offset + 16 ),
+					width: rUi( data, offset + 4 ),
+					height: rUi( data, offset + 8 )
+				};
+				var del = rUs( data, offset + 22 );
+				del = rUs( data, offset + 20 ) / ( del == 0 ? 100 : del );
+				var frm = {
+					rect: rct,
+					delay: Math.round( del * 1000 ),
+					dispose: data[ offset + 24 ],
+					blend: data[ offset + 25 ]
+				}; //console.log(frm);
+
+				out.frames.push( frm );
+
+			} else if ( type == 'fdAT' ) {
+
+				for ( var i = 0; i < len - 4; i ++ ) fd[ foff + i ] = data[ offset + i + 4 ];
+
+				foff += len - 4;
+
+			} else if ( type == 'pHYs' ) {
+
+				out.tabs[ type ] = [ bin.readUint( data, offset ), bin.readUint( data, offset + 4 ), data[ offset + 8 ] ];
+
+			} else if ( type == 'cHRM' ) {
+
+				out.tabs[ type ] = [];
+
+				for ( var i = 0; i < 8; i ++ ) out.tabs[ type ].push( bin.readUint( data, offset + i * 4 ) );
+
+			} else if ( type == 'tEXt' || type == 'zTXt' ) {
+
+				if ( out.tabs[ type ] == null ) out.tabs[ type ] = {};
+				var nz = bin.nextZero( data, offset );
+				var keyw = bin.readASCII( data, offset, nz - offset );
+				var text,
+					tl = offset + len - nz - 1;
+				if ( type == 'tEXt' ) text = bin.readASCII( data, nz + 1, tl ); else {
+
+					var bfr = UPNG.decode._inflate( data.slice( nz + 2, nz + 2 + tl ) );
+
+					text = bin.readUTF8( bfr, 0, bfr.length );
+
+				}
+
+				out.tabs[ type ][ keyw ] = text;
+
+			} else if ( type == 'iTXt' ) {
+
+				if ( out.tabs[ type ] == null ) out.tabs[ type ] = {};
+				var nz = 0,
+					off = offset;
+				nz = bin.nextZero( data, off );
+				var keyw = bin.readASCII( data, off, nz - off );
+				off = nz + 1;
+				var cflag = data[ off ];
+				off += 2;
+				nz = bin.nextZero( data, off );
+				bin.readASCII( data, off, nz - off );
+				off = nz + 1;
+				nz = bin.nextZero( data, off );
+				bin.readUTF8( data, off, nz - off );
+				off = nz + 1;
+				var text,
+					tl = len - ( off - offset );
+				if ( cflag == 0 ) text = bin.readUTF8( data, off, tl ); else {
+
+					var bfr = UPNG.decode._inflate( data.slice( off, off + tl ) );
+
+					text = bin.readUTF8( bfr, 0, bfr.length );
+
+				}
+
+				out.tabs[ type ][ keyw ] = text;
+
+			} else if ( type == 'PLTE' ) {
+
+				out.tabs[ type ] = bin.readBytes( data, offset, len );
+
+			} else if ( type == 'hIST' ) {
+
+				var pl = out.tabs[ 'PLTE' ].length / 3;
+				out.tabs[ type ] = [];
+
+				for ( var i = 0; i < pl; i ++ ) out.tabs[ type ].push( rUs( data, offset + i * 2 ) );
+
+			} else if ( type == 'tRNS' ) {
+
+				if ( out.ctype == 3 ) out.tabs[ type ] = bin.readBytes( data, offset, len ); else if ( out.ctype == 0 ) out.tabs[ type ] = rUs( data, offset ); else if ( out.ctype == 2 ) out.tabs[ type ] = [ rUs( data, offset ), rUs( data, offset + 2 ), rUs( data, offset + 4 ) ]; //else console.log("tRNS for unsupported color type",out.ctype, len);
+
+			} else if ( type == 'gAMA' ) out.tabs[ type ] = bin.readUint( data, offset ) / 100000; else if ( type == 'sRGB' ) out.tabs[ type ] = data[ offset ]; else if ( type == 'bKGD' ) {
+
+				if ( out.ctype == 0 || out.ctype == 4 ) out.tabs[ type ] = [ rUs( data, offset ) ]; else if ( out.ctype == 2 || out.ctype == 6 ) out.tabs[ type ] = [ rUs( data, offset ), rUs( data, offset + 2 ), rUs( data, offset + 4 ) ]; else if ( out.ctype == 3 ) out.tabs[ type ] = data[ offset ];
+
+			} else if ( type == 'IEND' ) {
+
+				break;
+
+			} //else {	log("unknown chunk type", type, len);	}
+
+
+			offset += len;
+			bin.readUint( data, offset );
+			offset += 4;
+
+		}
+
+		if ( foff != 0 ) {
+
+			var fr = out.frames[ out.frames.length - 1 ];
+			fr.data = UPNG.decode._decompress( out, fd.slice( 0, foff ), fr.rect.width, fr.rect.height );
+			foff = 0;
+
+		}
+
+		out.data = UPNG.decode._decompress( out, dd, out.width, out.height );
+		delete out.compress;
+		delete out.interlace;
+		delete out.filter;
+		return out;
+
+	};
+
+	UPNG.decode._decompress = function ( out, dd, w, h ) {
+
+		var bpp = UPNG.decode._getBPP( out ),
+			bpl = Math.ceil( w * bpp / 8 ),
+			buff = new Uint8Array( ( bpl + 1 + out.interlace ) * h );
+
+		if ( out.tabs[ 'CgBI' ] ) dd = UPNG.inflateRaw( dd, buff ); else dd = UPNG.decode._inflate( dd, buff );
+		if ( out.interlace == 0 ) dd = UPNG.decode._filterZero( dd, out, 0, w, h ); else if ( out.interlace == 1 ) dd = UPNG.decode._readInterlace( dd, out );
+		return dd;
+
+	};
+
+	UPNG.decode._inflate = function ( data, buff ) {
+
+		var out = UPNG[ 'inflateRaw' ]( new Uint8Array( data.buffer, 2, data.length - 6 ), buff );
+		return out;
+
+	};
+
+	UPNG.inflateRaw = function () {
+
+		var H = {};
+		H.H = {};
+
+		H.H.N = function ( N, W ) {
+
+			var R = Uint8Array,
+				i = 0,
+				m = 0,
+				J = 0,
+				h = 0,
+				Q = 0,
+				X = 0,
+				u = 0,
+				w = 0,
+				d = 0,
+				v,
+				C;
+			if ( N[ 0 ] == 3 && N[ 1 ] == 0 ) return W ? W : new R( 0 );
+			var V = H.H,
+				n = V.b,
+				A = V.e,
+				l = V.R,
+				M = V.n,
+				I = V.A,
+				e = V.Z,
+				b = V.m,
+				Z = W == null;
+			if ( Z ) W = new R( N.length >>> 2 << 5 );
+
+			while ( i == 0 ) {
+
+				i = n( N, d, 1 );
+				m = n( N, d + 1, 2 );
+				d += 3;
+
+				if ( m == 0 ) {
+
+					if ( ( d & 7 ) != 0 ) d += 8 - ( d & 7 );
+					var D = ( d >>> 3 ) + 4,
+						q = N[ D - 4 ] | N[ D - 3 ] << 8;
+					if ( Z ) W = H.H.W( W, w + q );
+					W.set( new R( N.buffer, N.byteOffset + D, q ), w );
+					d = D + q << 3;
+					w += q;
+					continue;
+
+				}
+
+				if ( Z ) W = H.H.W( W, w + ( 1 << 17 ) );
+
+				if ( m == 1 ) {
+
+					v = b.J;
+					C = b.h;
+					X = ( 1 << 9 ) - 1;
+					u = ( 1 << 5 ) - 1;
+
+				}
+
+				if ( m == 2 ) {
+
+					J = A( N, d, 5 ) + 257;
+					h = A( N, d + 5, 5 ) + 1;
+					Q = A( N, d + 10, 4 ) + 4;
+					d += 14;
+					var j = 1;
+
+					for ( var c = 0; c < 38; c += 2 ) {
+
+						b.Q[ c ] = 0;
+						b.Q[ c + 1 ] = 0;
+
+					}
+
+					for ( var c = 0; c < Q; c ++ ) {
+
+						var K = A( N, d + c * 3, 3 );
+						b.Q[ ( b.X[ c ] << 1 ) + 1 ] = K;
+						if ( K > j ) j = K;
+
+					}
+
+					d += 3 * Q;
+					M( b.Q, j );
+					I( b.Q, j, b.u );
+					v = b.w;
+					C = b.d;
+					d = l( b.u, ( 1 << j ) - 1, J + h, N, d, b.v );
+					var r = V.V( b.v, 0, J, b.C );
+					X = ( 1 << r ) - 1;
+					var S = V.V( b.v, J, h, b.D );
+					u = ( 1 << S ) - 1;
+					M( b.C, r );
+					I( b.C, r, v );
+					M( b.D, S );
+					I( b.D, S, C );
+
+				}
+
+				while ( ! 0 ) {
+
+					var T = v[ e( N, d ) & X ];
+					d += T & 15;
+					var p = T >>> 4;
+
+					if ( p >>> 8 == 0 ) {
+
+						W[ w ++ ] = p;
+
+					} else if ( p == 256 ) {
+
+						break;
+
+					} else {
+
+						var z = w + p - 254;
+
+						if ( p > 264 ) {
+
+							var _ = b.q[ p - 257 ];
+							z = w + ( _ >>> 3 ) + A( N, d, _ & 7 );
+							d += _ & 7;
+
+						}
+
+						var $ = C[ e( N, d ) & u ];
+						d += $ & 15;
+						var s = $ >>> 4,
+							Y = b.c[ s ],
+							a = ( Y >>> 4 ) + n( N, d, Y & 15 );
+						d += Y & 15;
+
+						while ( w < z ) {
+
+							W[ w ] = W[ w ++ - a ];
+							W[ w ] = W[ w ++ - a ];
+							W[ w ] = W[ w ++ - a ];
+							W[ w ] = W[ w ++ - a ];
+
+						}
+
+						w = z;
+
+					}
+
+				}
+
+			}
+
+			return W.length == w ? W : W.slice( 0, w );
+
+		};
+
+		H.H.W = function ( N, W ) {
+
+			var R = N.length;
+			if ( W <= R ) return N;
+			var V = new Uint8Array( R << 1 );
+			V.set( N, 0 );
+			return V;
+
+		};
+
+		H.H.R = function ( N, W, R, V, n, A ) {
+
+			var l = H.H.e,
+				M = H.H.Z,
+				I = 0;
+
+			while ( I < R ) {
+
+				var e = N[ M( V, n ) & W ];
+				n += e & 15;
+				var b = e >>> 4;
+
+				if ( b <= 15 ) {
+
+					A[ I ] = b;
+					I ++;
+
+				} else {
+
+					var Z = 0,
+						m = 0;
+
+					if ( b == 16 ) {
+
+						m = 3 + l( V, n, 2 );
+						n += 2;
+						Z = A[ I - 1 ];
+
+					} else if ( b == 17 ) {
+
+						m = 3 + l( V, n, 3 );
+						n += 3;
+
+					} else if ( b == 18 ) {
+
+						m = 11 + l( V, n, 7 );
+						n += 7;
+
+					}
+
+					var J = I + m;
+
+					while ( I < J ) {
+
+						A[ I ] = Z;
+						I ++;
+
+					}
+
+				}
+
+			}
+
+			return n;
+
+		};
+
+		H.H.V = function ( N, W, R, V ) {
+
+			var n = 0,
+				A = 0,
+				l = V.length >>> 1;
+
+			while ( A < R ) {
+
+				var M = N[ A + W ];
+				V[ A << 1 ] = 0;
+				V[ ( A << 1 ) + 1 ] = M;
+				if ( M > n ) n = M;
+				A ++;
+
+			}
+
+			while ( A < l ) {
+
+				V[ A << 1 ] = 0;
+				V[ ( A << 1 ) + 1 ] = 0;
+				A ++;
+
+			}
+
+			return n;
+
+		};
+
+		H.H.n = function ( N, W ) {
+
+			var R = H.H.m,
+				V = N.length,
+				n,
+				A,
+				l,
+				M,
+				I,
+				e = R.j;
+
+			for ( var M = 0; M <= W; M ++ ) e[ M ] = 0;
+
+			for ( M = 1; M < V; M += 2 ) e[ N[ M ] ] ++;
+
+			var b = R.K;
+			n = 0;
+			e[ 0 ] = 0;
+
+			for ( A = 1; A <= W; A ++ ) {
+
+				n = n + e[ A - 1 ] << 1;
+				b[ A ] = n;
+
+			}
+
+			for ( l = 0; l < V; l += 2 ) {
+
+				I = N[ l + 1 ];
+
+				if ( I != 0 ) {
+
+					N[ l ] = b[ I ];
+					b[ I ] ++;
+
+				}
+
+			}
+
+		};
+
+		H.H.A = function ( N, W, R ) {
+
+			var V = N.length,
+				n = H.H.m,
+				A = n.r;
+
+			for ( var l = 0; l < V; l += 2 ) if ( N[ l + 1 ] != 0 ) {
+
+				var M = l >> 1,
+					I = N[ l + 1 ],
+					e = M << 4 | I,
+					b = W - I,
+					Z = N[ l ] << b,
+					m = Z + ( 1 << b );
+
+				while ( Z != m ) {
+
+					var J = A[ Z ] >>> 15 - W;
+					R[ J ] = e;
+					Z ++;
+
+				}
+
+			}
+
+		};
+
+		H.H.l = function ( N, W ) {
+
+			var R = H.H.m.r,
+				V = 15 - W;
+
+			for ( var n = 0; n < N.length; n += 2 ) {
+
+				var A = N[ n ] << W - N[ n + 1 ];
+				N[ n ] = R[ A ] >>> V;
+
+			}
+
+		};
+
+		H.H.M = function ( N, W, R ) {
+
+			R = R << ( W & 7 );
+			var V = W >>> 3;
+			N[ V ] |= R;
+			N[ V + 1 ] |= R >>> 8;
+
+		};
+
+		H.H.I = function ( N, W, R ) {
+
+			R = R << ( W & 7 );
+			var V = W >>> 3;
+			N[ V ] |= R;
+			N[ V + 1 ] |= R >>> 8;
+			N[ V + 2 ] |= R >>> 16;
+
+		};
+
+		H.H.e = function ( N, W, R ) {
+
+			return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 ) >>> ( W & 7 ) & ( 1 << R ) - 1;
+
+		};
+
+		H.H.b = function ( N, W, R ) {
+
+			return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 ) >>> ( W & 7 ) & ( 1 << R ) - 1;
+
+		};
+
+		H.H.Z = function ( N, W ) {
+
+			return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 ) >>> ( W & 7 );
+
+		};
+
+		H.H.i = function ( N, W ) {
+
+			return ( N[ W >>> 3 ] | N[ ( W >>> 3 ) + 1 ] << 8 | N[ ( W >>> 3 ) + 2 ] << 16 | N[ ( W >>> 3 ) + 3 ] << 24 ) >>> ( W & 7 );
+
+		};
+
+		H.H.m = function () {
+
+			var N = Uint16Array,
+				W = Uint32Array;
+			return {
+				K: new N( 16 ),
+				j: new N( 16 ),
+				X: [ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 ],
+				S: [ 3, 4, 5, 6, 7, 8, 9, 10, 11, 13, 15, 17, 19, 23, 27, 31, 35, 43, 51, 59, 67, 83, 99, 115, 131, 163, 195, 227, 258, 999, 999, 999 ],
+				T: [ 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 5, 5, 5, 5, 0, 0, 0, 0 ],
+				q: new N( 32 ),
+				p: [ 1, 2, 3, 4, 5, 7, 9, 13, 17, 25, 33, 49, 65, 97, 129, 193, 257, 385, 513, 769, 1025, 1537, 2049, 3073, 4097, 6145, 8193, 12289, 16385, 24577, 65535, 65535 ],
+				z: [ 0, 0, 0, 0, 1, 1, 2, 2, 3, 3, 4, 4, 5, 5, 6, 6, 7, 7, 8, 8, 9, 9, 10, 10, 11, 11, 12, 12, 13, 13, 0, 0 ],
+				c: new W( 32 ),
+				J: new N( 512 ),
+				_: [],
+				h: new N( 32 ),
+				$: [],
+				w: new N( 32768 ),
+				C: [],
+				v: [],
+				d: new N( 32768 ),
+				D: [],
+				u: new N( 512 ),
+				Q: [],
+				r: new N( 1 << 15 ),
+				s: new W( 286 ),
+				Y: new W( 30 ),
+				a: new W( 19 ),
+				t: new W( 15e3 ),
+				k: new N( 1 << 16 ),
+				g: new N( 1 << 15 )
+			};
+
+		}();
+
+		( function () {
+
+			var N = H.H.m,
+				W = 1 << 15;
+
+			for ( var R = 0; R < W; R ++ ) {
+
+				var V = R;
+				V = ( V & 2863311530 ) >>> 1 | ( V & 1431655765 ) << 1;
+				V = ( V & 3435973836 ) >>> 2 | ( V & 858993459 ) << 2;
+				V = ( V & 4042322160 ) >>> 4 | ( V & 252645135 ) << 4;
+				V = ( V & 4278255360 ) >>> 8 | ( V & 16711935 ) << 8;
+				N.r[ R ] = ( V >>> 16 | V << 16 ) >>> 17;
+
+			}
+
+			function n( A, l, M ) {
+
+				while ( l -- != 0 ) A.push( 0, M );
+
+			}
+
+			for ( var R = 0; R < 32; R ++ ) {
+
+				N.q[ R ] = N.S[ R ] << 3 | N.T[ R ];
+				N.c[ R ] = N.p[ R ] << 4 | N.z[ R ];
+
+			}
+
+			n( N._, 144, 8 );
+			n( N._, 255 - 143, 9 );
+			n( N._, 279 - 255, 7 );
+			n( N._, 287 - 279, 8 );
+			H.H.n( N._, 9 );
+			H.H.A( N._, 9, N.J );
+			H.H.l( N._, 9 );
+			n( N.$, 32, 5 );
+			H.H.n( N.$, 5 );
+			H.H.A( N.$, 5, N.h );
+			H.H.l( N.$, 5 );
+			n( N.Q, 19, 0 );
+			n( N.C, 286, 0 );
+			n( N.D, 30, 0 );
+			n( N.v, 320, 0 );
+
+		} )();
+
+		return H.H.N;
+
+	}();
+
+	UPNG.decode._readInterlace = function ( data, out ) {
+
+		var w = out.width,
+			h = out.height;
+
+		var bpp = UPNG.decode._getBPP( out ),
+			cbpp = bpp >> 3,
+			bpl = Math.ceil( w * bpp / 8 );
+
+		var img = new Uint8Array( h * bpl );
+		var di = 0;
+		var starting_row = [ 0, 0, 4, 0, 2, 0, 1 ];
+		var starting_col = [ 0, 4, 0, 2, 0, 1, 0 ];
+		var row_increment = [ 8, 8, 8, 4, 4, 2, 2 ];
+		var col_increment = [ 8, 8, 4, 4, 2, 2, 1 ];
+		var pass = 0;
+
+		while ( pass < 7 ) {
+
+			var ri = row_increment[ pass ],
+				ci = col_increment[ pass ];
+			var sw = 0,
+				sh = 0;
+			var cr = starting_row[ pass ];
+
+			while ( cr < h ) {
+
+				cr += ri;
+				sh ++;
+
+			}
+
+			var cc = starting_col[ pass ];
+
+			while ( cc < w ) {
+
+				cc += ci;
+				sw ++;
+
+			}
+
+			var bpll = Math.ceil( sw * bpp / 8 );
+
+			UPNG.decode._filterZero( data, out, di, sw, sh );
+
+			var y = 0,
+				row = starting_row[ pass ];
+
+			while ( row < h ) {
+
+				var col = starting_col[ pass ];
+				var cdi = di + y * bpll << 3;
+
+				while ( col < w ) {
+
+					if ( bpp == 1 ) {
+
+						var val = data[ cdi >> 3 ];
+						val = val >> 7 - ( cdi & 7 ) & 1;
+						img[ row * bpl + ( col >> 3 ) ] |= val << 7 - ( ( col & 7 ) << 0 );
+
+					}
+
+					if ( bpp == 2 ) {
+
+						var val = data[ cdi >> 3 ];
+						val = val >> 6 - ( cdi & 7 ) & 3;
+						img[ row * bpl + ( col >> 2 ) ] |= val << 6 - ( ( col & 3 ) << 1 );
+
+					}
+
+					if ( bpp == 4 ) {
+
+						var val = data[ cdi >> 3 ];
+						val = val >> 4 - ( cdi & 7 ) & 15;
+						img[ row * bpl + ( col >> 1 ) ] |= val << 4 - ( ( col & 1 ) << 2 );
+
+					}
+
+					if ( bpp >= 8 ) {
+
+						var ii = row * bpl + col * cbpp;
+
+						for ( var j = 0; j < cbpp; j ++ ) img[ ii + j ] = data[ ( cdi >> 3 ) + j ];
+
+					}
+
+					cdi += bpp;
+					col += ci;
+
+				}
+
+				y ++;
+				row += ri;
+
+			}
+
+			if ( sw * sh != 0 ) di += sh * ( 1 + bpll );
+			pass = pass + 1;
+
+		}
+
+		return img;
+
+	};
+
+	UPNG.decode._getBPP = function ( out ) {
+
+		var noc = [ 1, null, 3, 1, 2, null, 4 ][ out.ctype ];
+		return noc * out.depth;
+
+	};
+
+	UPNG.decode._filterZero = function ( data, out, off, w, h ) {
+
+		var bpp = UPNG.decode._getBPP( out ),
+			bpl = Math.ceil( w * bpp / 8 ),
+			paeth = UPNG.decode._paeth;
+
+		bpp = Math.ceil( bpp / 8 );
+		var i = 0,
+			di = 1,
+			type = data[ off ],
+			x = 0;
+		if ( type > 1 ) data[ off ] = [ 0, 0, 1 ][ type - 2 ];
+		if ( type == 3 ) for ( x = bpp; x < bpl; x ++ ) data[ x + 1 ] = data[ x + 1 ] + ( data[ x + 1 - bpp ] >>> 1 ) & 255;
+
+		for ( var y = 0; y < h; y ++ ) {
+
+			i = off + y * bpl;
+			di = i + y + 1;
+			type = data[ di - 1 ];
+			x = 0;
+			if ( type == 0 ) for ( ; x < bpl; x ++ ) data[ i + x ] = data[ di + x ]; else if ( type == 1 ) {
+
+				for ( ; x < bpp; x ++ ) data[ i + x ] = data[ di + x ];
+
+				for ( ; x < bpl; x ++ ) data[ i + x ] = data[ di + x ] + data[ i + x - bpp ];
+
+			} else if ( type == 2 ) {
+
+				for ( ; x < bpl; x ++ ) data[ i + x ] = data[ di + x ] + data[ i + x - bpl ];
+
+			} else if ( type == 3 ) {
+
+				for ( ; x < bpp; x ++ ) data[ i + x ] = data[ di + x ] + ( data[ i + x - bpl ] >>> 1 );
+
+				for ( ; x < bpl; x ++ ) data[ i + x ] = data[ di + x ] + ( data[ i + x - bpl ] + data[ i + x - bpp ] >>> 1 );
+
+			} else {
+
+				for ( ; x < bpp; x ++ ) data[ i + x ] = data[ di + x ] + paeth( 0, data[ i + x - bpl ], 0 );
+
+				for ( ; x < bpl; x ++ ) data[ i + x ] = data[ di + x ] + paeth( data[ i + x - bpp ], data[ i + x - bpl ], data[ i + x - bpp - bpl ] );
+
+			}
+
+		}
+
+		return data;
+
+	};
+
+	UPNG.decode._paeth = function ( a, b, c ) {
+
+		var p = a + b - c,
+			pa = p - a,
+			pb = p - b,
+			pc = p - c;
+		if ( pa * pa <= pb * pb && pa * pa <= pc * pc ) return a; else if ( pb * pb <= pc * pc ) return b;
+		return c;
+
+	};
+
+	UPNG.decode._IHDR = function ( data, offset, out ) {
+
+		var bin = UPNG._bin;
+		out.width = bin.readUint( data, offset );
+		offset += 4;
+		out.height = bin.readUint( data, offset );
+		offset += 4;
+		out.depth = data[ offset ];
+		offset ++;
+		out.ctype = data[ offset ];
+		offset ++;
+		out.compress = data[ offset ];
+		offset ++;
+		out.filter = data[ offset ];
+		offset ++;
+		out.interlace = data[ offset ];
+		offset ++;
+
+	};
+
+	UPNG._bin = {
+		nextZero: function ( data, p ) {
+
+			while ( data[ p ] != 0 ) p ++;
+
+			return p;
+
+		},
+		readUshort: function ( buff, p ) {
+
+			return buff[ p ] << 8 | buff[ p + 1 ];
+
+		},
+		writeUshort: function ( buff, p, n ) {
+
+			buff[ p ] = n >> 8 & 255;
+			buff[ p + 1 ] = n & 255;
+
+		},
+		readUint: function ( buff, p ) {
+
+			return buff[ p ] * ( 256 * 256 * 256 ) + ( buff[ p + 1 ] << 16 | buff[ p + 2 ] << 8 | buff[ p + 3 ] );
+
+		},
+		writeUint: function ( buff, p, n ) {
+
+			buff[ p ] = n >> 24 & 255;
+			buff[ p + 1 ] = n >> 16 & 255;
+			buff[ p + 2 ] = n >> 8 & 255;
+			buff[ p + 3 ] = n & 255;
+
+		},
+		readASCII: function ( buff, p, l ) {
+
+			var s = '';
+
+			for ( var i = 0; i < l; i ++ ) s += String.fromCharCode( buff[ p + i ] );
+
+			return s;
+
+		},
+		writeASCII: function ( data, p, s ) {
+
+			for ( var i = 0; i < s.length; i ++ ) data[ p + i ] = s.charCodeAt( i );
+
+		},
+		readBytes: function ( buff, p, l ) {
+
+			var arr = [];
+
+			for ( var i = 0; i < l; i ++ ) arr.push( buff[ p + i ] );
+
+			return arr;
+
+		},
+		pad: function ( n ) {
+
+			return n.length < 2 ? '0' + n : n;
+
+		},
+		readUTF8: function ( buff, p, l ) {
+
+			var s = '',
+				ns;
+
+			for ( var i = 0; i < l; i ++ ) s += '%' + UPNG._bin.pad( buff[ p + i ].toString( 16 ) );
+
+			try {
+
+				ns = decodeURIComponent( s );
+
+			} catch ( e ) {
+
+				return UPNG._bin.readASCII( buff, p, l );
+
+			}
+
+			return ns;
+
+		}
+	};
+
+	UPNG._copyTile = function ( sb, sw, sh, tb, tw, th, xoff, yoff, mode ) {
+
+		var w = Math.min( sw, tw ),
+			h = Math.min( sh, th );
+		var si = 0,
+			ti = 0;
+
+		for ( var y = 0; y < h; y ++ ) for ( var x = 0; x < w; x ++ ) {
+
+			if ( xoff >= 0 && yoff >= 0 ) {
+
+				si = y * sw + x << 2;
+				ti = ( yoff + y ) * tw + xoff + x << 2;
+
+			} else {
+
+				si = ( - yoff + y ) * sw - xoff + x << 2;
+				ti = y * tw + x << 2;
+
+			}
+
+			if ( mode == 0 ) {
+
+				tb[ ti ] = sb[ si ];
+				tb[ ti + 1 ] = sb[ si + 1 ];
+				tb[ ti + 2 ] = sb[ si + 2 ];
+				tb[ ti + 3 ] = sb[ si + 3 ];
+
+			} else if ( mode == 1 ) {
+
+				var fa = sb[ si + 3 ] * ( 1 / 255 ),
+					fr = sb[ si ] * fa,
+					fg = sb[ si + 1 ] * fa,
+					fb = sb[ si + 2 ] * fa;
+				var ba = tb[ ti + 3 ] * ( 1 / 255 ),
+					br = tb[ ti ] * ba,
+					bg = tb[ ti + 1 ] * ba,
+					bb = tb[ ti + 2 ] * ba;
+				var ifa = 1 - fa,
+					oa = fa + ba * ifa,
+					ioa = oa == 0 ? 0 : 1 / oa;
+				tb[ ti + 3 ] = 255 * oa;
+				tb[ ti + 0 ] = ( fr + br * ifa ) * ioa;
+				tb[ ti + 1 ] = ( fg + bg * ifa ) * ioa;
+				tb[ ti + 2 ] = ( fb + bb * ifa ) * ioa;
+
+			} else if ( mode == 2 ) {
+
+				// copy only differences, otherwise zero
+				var fa = sb[ si + 3 ],
+					fr = sb[ si ],
+					fg = sb[ si + 1 ],
+					fb = sb[ si + 2 ];
+				var ba = tb[ ti + 3 ],
+					br = tb[ ti ],
+					bg = tb[ ti + 1 ],
+					bb = tb[ ti + 2 ];
+
+				if ( fa == ba && fr == br && fg == bg && fb == bb ) {
+
+					tb[ ti ] = 0;
+					tb[ ti + 1 ] = 0;
+					tb[ ti + 2 ] = 0;
+					tb[ ti + 3 ] = 0;
+
+				} else {
+
+					tb[ ti ] = fr;
+					tb[ ti + 1 ] = fg;
+					tb[ ti + 2 ] = fb;
+					tb[ ti + 3 ] = fa;
+
+				}
+
+			} else if ( mode == 3 ) {
+
+				// check if can be blended
+				var fa = sb[ si + 3 ],
+					fr = sb[ si ],
+					fg = sb[ si + 1 ],
+					fb = sb[ si + 2 ];
+				var ba = tb[ ti + 3 ],
+					br = tb[ ti ],
+					bg = tb[ ti + 1 ],
+					bb = tb[ ti + 2 ];
+				if ( fa == ba && fr == br && fg == bg && fb == bb ) continue; //if(fa!=255 && ba!=0) return false;
+
+				if ( fa < 220 && ba > 20 ) return false;
+
+			}
+
+		}
+
+		return true;
+
+	};
+
+	THREE.RGBMLoader = RGBMLoader;
+
+} )();

+ 218 - 238
examples/js/loaders/STLLoader.js

@@ -1,4 +1,6 @@
-/**
+( function () {
+
+	/**
  * Description: A THREE loader for STL ASCII files, as created by Solidworks and other CAD programs.
  *
  * Supports both binary and ASCII encoded files, with automatic detection of type.
@@ -6,22 +8,22 @@
  * The loader returns a non-indexed buffer geometry.
  *
  * Limitations:
- *  Binary decoding supports "Magics" color format (http://en.wikipedia.org/wiki/STL_(file_format)#Color_in_binary_STL).
- *  There is perhaps some question as to how valid it is to always assume little-endian-ness.
- *  ASCII decoding assumes file is UTF-8.
+ *	Binary decoding supports "Magics" color format (http://en.wikipedia.org/wiki/STL_(file_format)#Color_in_binary_STL).
+ *	There is perhaps some question as to how valid it is to always assume little-endian-ness.
+ *	ASCII decoding assumes file is UTF-8.
  *
  * Usage:
- *  var loader = new THREE.STLLoader();
- *  loader.load( './models/stl/slotted_disk.stl', function ( geometry ) {
- *    scene.add( new THREE.Mesh( geometry ) );
- *  });
+ *	var loader = new STLLoader();
+ *	loader.load( './models/stl/slotted_disk.stl', function ( geometry ) {
+ *		scene.add( new THREE.Mesh( geometry ) );
+ *	});
  *
  * For binary STLs geometry might contain colors for vertices. To use it:
- *  // use the same code to load STL as above
- *  if (geometry.hasColors) {
- *    material = new THREE.MeshPhongMaterial({ opacity: geometry.alpha, vertexColors: true });
- *  } else { .... }
- *  var mesh = new THREE.Mesh( geometry, material );
+ *	// use the same code to load STL as above
+ *	if (geometry.hasColors) {
+ *		material = new THREE.MeshPhongMaterial({ opacity: geometry.alpha, vertexColors: true });
+ *	} else { .... }
+ *	var mesh = new THREE.Mesh( geometry, material );
  *
  * For ASCII STLs containing multiple solids, each solid is assigned to a different group.
  * Groups can be used to assign a different color by defining an array of materials with the same length of
@@ -31,360 +33,338 @@
  *
  * For example:
  *
- *  var materials = [];
- *  var nGeometryGroups = geometry.groups.length;
+ *	var materials = [];
+ *	var nGeometryGroups = geometry.groups.length;
  *
- *  var colorMap = ...; // Some logic to index colors.
+ *	var colorMap = ...; // Some logic to index colors.
  *
- *  for (var i = 0; i < nGeometryGroups; i++) {
+ *	for (var i = 0; i < nGeometryGroups; i++) {
  *
  *		var material = new THREE.MeshPhongMaterial({
  *			color: colorMap[i],
  *			wireframe: false
  *		});
  *
- *  }
+ *	}
  *
- *  materials.push(material);
- *  var mesh = new THREE.Mesh(geometry, materials);
+ *	materials.push(material);
+ *	var mesh = new THREE.Mesh(geometry, materials);
  */
 
+	var STLLoader = function ( manager ) {
 
-THREE.STLLoader = function ( manager ) {
-
-	THREE.Loader.call( this, manager );
-
-};
+		THREE.Loader.call( this, manager );
 
-THREE.STLLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+	};
 
-	constructor: THREE.STLLoader,
+	STLLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+		constructor: STLLoader,
+		load: function ( url, onLoad, onProgress, onError ) {
 
-	load: function ( url, onLoad, onProgress, onError ) {
+			var scope = this;
+			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 ( text ) {
 
-		var scope = this;
+				try {
 
-		var loader = new THREE.FileLoader( this.manager );
-		loader.setPath( this.path );
-		loader.setResponseType( 'arraybuffer' );
-		loader.setRequestHeader( this.requestHeader );
-		loader.setWithCredentials( this.withCredentials );
+					onLoad( scope.parse( text ) );
 
-		loader.load( url, function ( text ) {
+				} catch ( e ) {
 
-			try {
+					if ( onError ) {
 
-				onLoad( scope.parse( text ) );
+						onError( e );
 
-			} catch ( e ) {
-
-				if ( onError ) {
+					} else {
 
-					onError( e );
+						console.error( e );
 
-				} else {
+					}
 
-					console.error( e );
+					scope.manager.itemError( url );
 
 				}
 
-				scope.manager.itemError( url );
-
-			}
-
-		}, onProgress, onError );
+			}, onProgress, onError );
 
-	},
+		},
+		parse: function ( data ) {
 
-	parse: function ( data ) {
+			function isBinary( data ) {
 
-		function isBinary( data ) {
+				var expect, face_size, n_faces, reader;
+				reader = new DataView( data );
+				face_size = 32 / 8 * 3 + 32 / 8 * 3 * 3 + 16 / 8;
+				n_faces = reader.getUint32( 80, true );
+				expect = 80 + 32 / 8 + n_faces * face_size;
 
-			var expect, face_size, n_faces, reader;
-			reader = new DataView( data );
-			face_size = ( 32 / 8 * 3 ) + ( ( 32 / 8 * 3 ) * 3 ) + ( 16 / 8 );
-			n_faces = reader.getUint32( 80, true );
-			expect = 80 + ( 32 / 8 ) + ( n_faces * face_size );
+				if ( expect === reader.byteLength ) {
 
-			if ( expect === reader.byteLength ) {
+					return true;
 
-				return true;
+				} // An ASCII STL data must begin with 'solid ' as the first six bytes.
+				// However, ASCII STLs lacking the SPACE after the 'd' are known to be
+				// plentiful.	So, check the first 5 bytes for 'solid'.
+				// Several encodings, such as UTF-8, precede the text with up to 5 bytes:
+				// https://en.wikipedia.org/wiki/Byte_order_mark#Byte_order_marks_by_encoding
+				// Search for "solid" to start anywhere after those prefixes.
+				// US-ASCII ordinal values for 's', 'o', 'l', 'i', 'd'
 
-			}
 
-			// An ASCII STL data must begin with 'solid ' as the first six bytes.
-			// However, ASCII STLs lacking the SPACE after the 'd' are known to be
-			// plentiful.  So, check the first 5 bytes for 'solid'.
+				var solid = [ 115, 111, 108, 105, 100 ];
 
-			// Several encodings, such as UTF-8, precede the text with up to 5 bytes:
-			// https://en.wikipedia.org/wiki/Byte_order_mark#Byte_order_marks_by_encoding
-			// Search for "solid" to start anywhere after those prefixes.
+				for ( var off = 0; off < 5; off ++ ) {
 
-			// US-ASCII ordinal values for 's', 'o', 'l', 'i', 'd'
+					// If "solid" text is matched to the current offset, declare it to be an ASCII STL.
+					if ( matchDataViewAt( solid, reader, off ) ) return false;
 
-			var solid = [ 115, 111, 108, 105, 100 ];
+				} // Couldn't find "solid" text at the beginning; it is binary STL.
 
-			for ( var off = 0; off < 5; off ++ ) {
 
-				// If "solid" text is matched to the current offset, declare it to be an ASCII STL.
-
-				if ( matchDataViewAt( solid, reader, off ) ) return false;
+				return true;
 
 			}
 
-			// Couldn't find "solid" text at the beginning; it is binary STL.
+			function matchDataViewAt( query, reader, offset ) {
 
-			return true;
+				// Check if each byte in query matches the corresponding byte from the current offset
+				for ( var i = 0, il = query.length; i < il; i ++ ) {
 
-		}
-
-		function matchDataViewAt( query, reader, offset ) {
+					if ( query[ i ] !== reader.getUint8( offset + i, false ) ) return false;
 
-			// Check if each byte in query matches the corresponding byte from the current offset
-
-			for ( var i = 0, il = query.length; i < il; i ++ ) {
+				}
 
-				if ( query[ i ] !== reader.getUint8( offset + i, false ) ) return false;
+				return true;
 
 			}
 
-			return true;
-
-		}
-
-		function parseBinary( data ) {
-
-			var reader = new DataView( data );
-			var faces = reader.getUint32( 80, true );
-
-			var r, g, b, hasColors = false, colors;
-			var defaultR, defaultG, defaultB, alpha;
-
-			// process STL header
-			// check for default color in header ("COLOR=rgba" sequence).
-
-			for ( var index = 0; index < 80 - 10; index ++ ) {
-
-				if ( ( reader.getUint32( index, false ) == 0x434F4C4F /*COLO*/ ) &&
-					( reader.getUint8( index + 4 ) == 0x52 /*'R'*/ ) &&
-					( reader.getUint8( index + 5 ) == 0x3D /*'='*/ ) ) {
+			function parseBinary( data ) {
+
+				var reader = new DataView( data );
+				var faces = reader.getUint32( 80, true );
+				var r,
+					g,
+					b,
+					hasColors = false,
+					colors;
+				var defaultR, defaultG, defaultB, alpha; // process STL header
+				// check for default color in header ("COLOR=rgba" sequence).
+
+				for ( var index = 0; index < 80 - 10; index ++ ) {
+
+					if ( reader.getUint32( index, false ) == 0x434F4C4F
+				/*COLO*/
+				&& reader.getUint8( index + 4 ) == 0x52
+				/*'R'*/
+				&& reader.getUint8( index + 5 ) == 0x3D
+				/*'='*/
+					) {
+
+						hasColors = true;
+						colors = new Float32Array( faces * 3 * 3 );
+						defaultR = reader.getUint8( index + 6 ) / 255;
+						defaultG = reader.getUint8( index + 7 ) / 255;
+						defaultB = reader.getUint8( index + 8 ) / 255;
+						alpha = reader.getUint8( index + 9 ) / 255;
 
-					hasColors = true;
-					colors = new Float32Array( faces * 3 * 3 );
-
-					defaultR = reader.getUint8( index + 6 ) / 255;
-					defaultG = reader.getUint8( index + 7 ) / 255;
-					defaultB = reader.getUint8( index + 8 ) / 255;
-					alpha = reader.getUint8( index + 9 ) / 255;
+					}
 
 				}
 
-			}
-
-			var dataOffset = 84;
-			var faceLength = 12 * 4 + 2;
+				var dataOffset = 84;
+				var faceLength = 12 * 4 + 2;
+				var geometry = new THREE.BufferGeometry();
+				var vertices = new Float32Array( faces * 3 * 3 );
+				var normals = new Float32Array( faces * 3 * 3 );
 
-			var geometry = new THREE.BufferGeometry();
+				for ( var face = 0; face < faces; face ++ ) {
 
-			var vertices = new Float32Array( faces * 3 * 3 );
-			var normals = new Float32Array( faces * 3 * 3 );
+					var start = dataOffset + face * faceLength;
+					var normalX = reader.getFloat32( start, true );
+					var normalY = reader.getFloat32( start + 4, true );
+					var normalZ = reader.getFloat32( start + 8, true );
 
-			for ( var face = 0; face < faces; face ++ ) {
-
-				var start = dataOffset + face * faceLength;
-				var normalX = reader.getFloat32( start, true );
-				var normalY = reader.getFloat32( start + 4, true );
-				var normalZ = reader.getFloat32( start + 8, true );
-
-				if ( hasColors ) {
+					if ( hasColors ) {
 
-					var packedColor = reader.getUint16( start + 48, true );
+						var packedColor = reader.getUint16( start + 48, true );
 
-					if ( ( packedColor & 0x8000 ) === 0 ) {
+						if ( ( packedColor & 0x8000 ) === 0 ) {
 
-						// facet has its own unique color
+							// facet has its own unique color
+							r = ( packedColor & 0x1F ) / 31;
+							g = ( packedColor >> 5 & 0x1F ) / 31;
+							b = ( packedColor >> 10 & 0x1F ) / 31;
 
-						r = ( packedColor & 0x1F ) / 31;
-						g = ( ( packedColor >> 5 ) & 0x1F ) / 31;
-						b = ( ( packedColor >> 10 ) & 0x1F ) / 31;
+						} else {
 
-					} else {
+							r = defaultR;
+							g = defaultG;
+							b = defaultB;
 
-						r = defaultR;
-						g = defaultG;
-						b = defaultB;
+						}
 
 					}
 
-				}
-
-				for ( var i = 1; i <= 3; i ++ ) {
+					for ( var i = 1; i <= 3; i ++ ) {
 
-					var vertexstart = start + i * 12;
-					var componentIdx = ( face * 3 * 3 ) + ( ( i - 1 ) * 3 );
+						var vertexstart = start + i * 12;
+						var componentIdx = face * 3 * 3 + ( i - 1 ) * 3;
+						vertices[ componentIdx ] = reader.getFloat32( vertexstart, true );
+						vertices[ componentIdx + 1 ] = reader.getFloat32( vertexstart + 4, true );
+						vertices[ componentIdx + 2 ] = reader.getFloat32( vertexstart + 8, true );
+						normals[ componentIdx ] = normalX;
+						normals[ componentIdx + 1 ] = normalY;
+						normals[ componentIdx + 2 ] = normalZ;
 
-					vertices[ componentIdx ] = reader.getFloat32( vertexstart, true );
-					vertices[ componentIdx + 1 ] = reader.getFloat32( vertexstart + 4, true );
-					vertices[ componentIdx + 2 ] = reader.getFloat32( vertexstart + 8, true );
+						if ( hasColors ) {
 
-					normals[ componentIdx ] = normalX;
-					normals[ componentIdx + 1 ] = normalY;
-					normals[ componentIdx + 2 ] = normalZ;
-
-					if ( hasColors ) {
+							colors[ componentIdx ] = r;
+							colors[ componentIdx + 1 ] = g;
+							colors[ componentIdx + 2 ] = b;
 
-						colors[ componentIdx ] = r;
-						colors[ componentIdx + 1 ] = g;
-						colors[ componentIdx + 2 ] = b;
+						}
 
 					}
 
 				}
 
-			}
-
-			geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
-			geometry.setAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ) );
+				geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
+				geometry.setAttribute( 'normal', new THREE.BufferAttribute( normals, 3 ) );
 
-			if ( hasColors ) {
-
-				geometry.setAttribute( 'color', new THREE.BufferAttribute( colors, 3 ) );
-				geometry.hasColors = true;
-				geometry.alpha = alpha;
+				if ( hasColors ) {
 
-			}
+					geometry.setAttribute( 'color', new THREE.BufferAttribute( colors, 3 ) );
+					geometry.hasColors = true;
+					geometry.alpha = alpha;
 
-			return geometry;
+				}
 
-		}
+				return geometry;
 
-		function parseASCII( data ) {
+			}
 
-			var geometry = new THREE.BufferGeometry();
-			var patternSolid = /solid([\s\S]*?)endsolid/g;
-			var patternFace = /facet([\s\S]*?)endfacet/g;
-			var faceCounter = 0;
+			function parseASCII( data ) {
 
-			var patternFloat = /[\s]+([+-]?(?:\d*)(?:\.\d*)?(?:[eE][+-]?\d+)?)/.source;
-			var patternVertex = new RegExp( 'vertex' + patternFloat + patternFloat + patternFloat, 'g' );
-			var patternNormal = new RegExp( 'normal' + patternFloat + patternFloat + patternFloat, 'g' );
+				var geometry = new THREE.BufferGeometry();
+				var patternSolid = /solid([\s\S]*?)endsolid/g;
+				var patternFace = /facet([\s\S]*?)endfacet/g;
+				var faceCounter = 0;
+				var patternFloat = /[\s]+([+-]?(?:\d*)(?:\.\d*)?(?:[eE][+-]?\d+)?)/.source;
+				var patternVertex = new RegExp( 'vertex' + patternFloat + patternFloat + patternFloat, 'g' );
+				var patternNormal = new RegExp( 'normal' + patternFloat + patternFloat + patternFloat, 'g' );
+				var vertices = [];
+				var normals = [];
+				var normal = new THREE.Vector3();
+				var result;
+				var groupCount = 0;
+				var startVertex = 0;
+				var endVertex = 0;
 
-			var vertices = [];
-			var normals = [];
+				while ( ( result = patternSolid.exec( data ) ) !== null ) {
 
-			var normal = new THREE.Vector3();
+					startVertex = endVertex;
+					var solid = result[ 0 ];
 
-			var result;
+					while ( ( result = patternFace.exec( solid ) ) !== null ) {
 
-			var groupCount = 0;
-			var startVertex = 0;
-			var endVertex = 0;
+						var vertexCountPerFace = 0;
+						var normalCountPerFace = 0;
+						var text = result[ 0 ];
 
-			while ( ( result = patternSolid.exec( data ) ) !== null ) {
+						while ( ( result = patternNormal.exec( text ) ) !== null ) {
 
-				startVertex = endVertex;
+							normal.x = parseFloat( result[ 1 ] );
+							normal.y = parseFloat( result[ 2 ] );
+							normal.z = parseFloat( result[ 3 ] );
+							normalCountPerFace ++;
 
-				var solid = result[ 0 ];
+						}
 
-				while ( ( result = patternFace.exec( solid ) ) !== null ) {
+						while ( ( result = patternVertex.exec( text ) ) !== null ) {
 
-					var vertexCountPerFace = 0;
-					var normalCountPerFace = 0;
+							vertices.push( parseFloat( result[ 1 ] ), parseFloat( result[ 2 ] ), parseFloat( result[ 3 ] ) );
+							normals.push( normal.x, normal.y, normal.z );
+							vertexCountPerFace ++;
+							endVertex ++;
 
-					var text = result[ 0 ];
+						} // every face have to own ONE valid normal
 
-					while ( ( result = patternNormal.exec( text ) ) !== null ) {
 
-						normal.x = parseFloat( result[ 1 ] );
-						normal.y = parseFloat( result[ 2 ] );
-						normal.z = parseFloat( result[ 3 ] );
-						normalCountPerFace ++;
+						if ( normalCountPerFace !== 1 ) {
 
-					}
+							console.error( 'THREE.STLLoader: Something isn\'t right with the normal of face number ' + faceCounter );
 
-					while ( ( result = patternVertex.exec( text ) ) !== null ) {
+						} // each face have to own THREE valid vertices
 
-						vertices.push( parseFloat( result[ 1 ] ), parseFloat( result[ 2 ] ), parseFloat( result[ 3 ] ) );
-						normals.push( normal.x, normal.y, normal.z );
-						vertexCountPerFace ++;
-						endVertex ++;
 
-					}
+						if ( vertexCountPerFace !== 3 ) {
 
-					// every face have to own ONE valid normal
+							console.error( 'THREE.STLLoader: Something isn\'t right with the vertices of face number ' + faceCounter );
 
-					if ( normalCountPerFace !== 1 ) {
+						}
 
-						console.error( 'THREE.STLLoader: Something isn\'t right with the normal of face number ' + faceCounter );
+						faceCounter ++;
 
 					}
 
-					// each face have to own THREE valid vertices
-
-					if ( vertexCountPerFace !== 3 ) {
-
-						console.error( 'THREE.STLLoader: Something isn\'t right with the vertices of face number ' + faceCounter );
-
-					}
-
-					faceCounter ++;
+					var start = startVertex;
+					var count = endVertex - startVertex;
+					geometry.addGroup( start, count, groupCount );
+					groupCount ++;
 
 				}
 
-				var start = startVertex;
-				var count = endVertex - startVertex;
-
-				geometry.addGroup( start, count, groupCount );
-				groupCount ++;
+				geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
+				geometry.setAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) );
+				return geometry;
 
 			}
 
-			geometry.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
-			geometry.setAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) );
+			function ensureString( buffer ) {
 
-			return geometry;
-
-		}
+				if ( typeof buffer !== 'string' ) {
 
-		function ensureString( buffer ) {
+					return THREE.LoaderUtils.decodeText( new Uint8Array( buffer ) );
 
-			if ( typeof buffer !== 'string' ) {
+				}
 
-				return THREE.LoaderUtils.decodeText( new Uint8Array( buffer ) );
+				return buffer;
 
 			}
 
-			return buffer;
-
-		}
+			function ensureBinary( buffer ) {
 
-		function ensureBinary( buffer ) {
+				if ( typeof buffer === 'string' ) {
 
-			if ( typeof buffer === 'string' ) {
+					var array_buffer = new Uint8Array( buffer.length );
 
-				var array_buffer = new Uint8Array( buffer.length );
-				for ( var i = 0; i < buffer.length; i ++ ) {
+					for ( var i = 0; i < buffer.length; i ++ ) {
 
-					array_buffer[ i ] = buffer.charCodeAt( i ) & 0xff; // implicitly assumes little-endian
+						array_buffer[ i ] = buffer.charCodeAt( i ) & 0xff; // implicitly assumes little-endian
 
-				}
+					}
 
-				return array_buffer.buffer || array_buffer;
+					return array_buffer.buffer || array_buffer;
 
-			} else {
+				} else {
 
-				return buffer;
+					return buffer;
 
-			}
+				}
 
-		}
+			} // start
 
-		// start
 
-		var binData = ensureBinary( data );
+			var binData = ensureBinary( data );
+			return isBinary( binData ) ? parseBinary( binData ) : parseASCII( ensureString( data ) );
 
-		return isBinary( binData ) ? parseBinary( binData ) : parseASCII( ensureString( data ) );
+		}
+	} );
 
-	}
+	THREE.STLLoader = STLLoader;
 
-} );
+} )();

Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 359 - 450
examples/js/loaders/SVGLoader.js


Rozdielové dáta súboru neboli zobrazené, pretože súbor je príliš veľký
+ 424 - 466
examples/js/loaders/TDSLoader.js


+ 315 - 342
examples/js/loaders/TGALoader.js

@@ -1,513 +1,486 @@
-THREE.TGALoader = function ( manager ) {
+( function () {
 
-	THREE.DataTextureLoader.call( this, manager );
+	var TGALoader = function ( manager ) {
 
-};
+		THREE.DataTextureLoader.call( this, manager );
 
-THREE.TGALoader.prototype = Object.assign( Object.create( THREE.DataTextureLoader.prototype ), {
+	};
 
-	constructor: THREE.TGALoader,
+	TGALoader.prototype = Object.assign( Object.create( THREE.DataTextureLoader.prototype ), {
+		constructor: TGALoader,
+		parse: function ( buffer ) {
 
-	parse: function ( buffer ) {
+			// reference from vthibault, https://github.com/vthibault/roBrowser/blob/master/src/Loaders/Targa.js
+			function tgaCheckHeader( header ) {
 
-		// reference from vthibault, https://github.com/vthibault/roBrowser/blob/master/src/Loaders/Targa.js
+				switch ( header.image_type ) {
 
-		function tgaCheckHeader( header ) {
+					// check indexed type
+					case TGA_TYPE_INDEXED:
+					case TGA_TYPE_RLE_INDEXED:
+						if ( header.colormap_length > 256 || header.colormap_size !== 24 || header.colormap_type !== 1 ) {
 
-			switch ( header.image_type ) {
+							console.error( 'THREE.TGALoader: Invalid type colormap data for indexed type.' );
 
-				// check indexed type
+						}
 
-				case TGA_TYPE_INDEXED:
-				case TGA_TYPE_RLE_INDEXED:
-					if ( header.colormap_length > 256 || header.colormap_size !== 24 || header.colormap_type !== 1 ) {
+						break;
+						// check colormap type
 
-						console.error( 'THREE.TGALoader: Invalid type colormap data for indexed type.' );
+					case TGA_TYPE_RGB:
+					case TGA_TYPE_GREY:
+					case TGA_TYPE_RLE_RGB:
+					case TGA_TYPE_RLE_GREY:
+						if ( header.colormap_type ) {
 
-					}
+							console.error( 'THREE.TGALoader: Invalid type colormap data for colormap type.' );
 
-					break;
+						}
 
-					// check colormap type
+						break;
+						// What the need of a file without data ?
 
-				case TGA_TYPE_RGB:
-				case TGA_TYPE_GREY:
-				case TGA_TYPE_RLE_RGB:
-				case TGA_TYPE_RLE_GREY:
-					if ( header.colormap_type ) {
+					case TGA_TYPE_NO_DATA:
+						console.error( 'THREE.TGALoader: No data.' );
+						// Invalid type ?
 
-						console.error( 'THREE.TGALoader: Invalid type colormap data for colormap type.' );
+					default:
+						console.error( 'THREE.TGALoader: Invalid type "%s".', header.image_type );
 
-					}
+				} // check image width and height
 
-					break;
 
-					// What the need of a file without data ?
+				if ( header.width <= 0 || header.height <= 0 ) {
 
-				case TGA_TYPE_NO_DATA:
-					console.error( 'THREE.TGALoader: No data.' );
+					console.error( 'THREE.TGALoader: Invalid image size.' );
 
-					// Invalid type ?
+				} // check image pixel size
 
-				default:
-					console.error( 'THREE.TGALoader: Invalid type "%s".', header.image_type );
 
-			}
+				if ( header.pixel_size !== 8 && header.pixel_size !== 16 && header.pixel_size !== 24 && header.pixel_size !== 32 ) {
 
-			// check image width and height
+					console.error( 'THREE.TGALoader: Invalid pixel size "%s".', header.pixel_size );
 
-			if ( header.width <= 0 || header.height <= 0 ) {
+				}
 
-				console.error( 'THREE.TGALoader: Invalid image size.' );
+			} // parse tga image buffer
 
-			}
 
-			// check image pixel size
+			function tgaParse( use_rle, use_pal, header, offset, data ) {
 
-			if ( header.pixel_size !== 8 && header.pixel_size !== 16 &&
-				header.pixel_size !== 24 && header.pixel_size !== 32 ) {
+				var pixel_data, pixel_size, pixel_total, palettes;
+				pixel_size = header.pixel_size >> 3;
+				pixel_total = header.width * header.height * pixel_size; // read palettes
 
-				console.error( 'THREE.TGALoader: Invalid pixel size "%s".', header.pixel_size );
+				if ( use_pal ) {
 
-			}
+					palettes = data.subarray( offset, offset += header.colormap_length * ( header.colormap_size >> 3 ) );
 
-		}
+				} // read RLE
 
-		// parse tga image buffer
 
-		function tgaParse( use_rle, use_pal, header, offset, data ) {
+				if ( use_rle ) {
 
-			var pixel_data,
-				pixel_size,
-				pixel_total,
-				palettes;
+					pixel_data = new Uint8Array( pixel_total );
+					var c, count, i;
+					var shift = 0;
+					var pixels = new Uint8Array( pixel_size );
 
-			pixel_size = header.pixel_size >> 3;
-			pixel_total = header.width * header.height * pixel_size;
+					while ( shift < pixel_total ) {
 
-			 // read palettes
+						c = data[ offset ++ ];
+						count = ( c & 0x7f ) + 1; // RLE pixels
 
-			 if ( use_pal ) {
+						if ( c & 0x80 ) {
 
-				 palettes = data.subarray( offset, offset += header.colormap_length * ( header.colormap_size >> 3 ) );
+							// bind pixel tmp array
+							for ( i = 0; i < pixel_size; ++ i ) {
 
-			 }
+								pixels[ i ] = data[ offset ++ ];
 
-			 // read RLE
+							} // copy pixel array
 
-			 if ( use_rle ) {
 
-				 pixel_data = new Uint8Array( pixel_total );
+							for ( i = 0; i < count; ++ i ) {
 
-				var c, count, i;
-				var shift = 0;
-				var pixels = new Uint8Array( pixel_size );
+								pixel_data.set( pixels, shift + i * pixel_size );
 
-				while ( shift < pixel_total ) {
+							}
 
-					c = data[ offset ++ ];
-					count = ( c & 0x7f ) + 1;
+							shift += pixel_size * count;
 
-					// RLE pixels
+						} else {
 
-					if ( c & 0x80 ) {
+							// raw pixels
+							count *= pixel_size;
 
-						// bind pixel tmp array
+							for ( i = 0; i < count; ++ i ) {
 
-						for ( i = 0; i < pixel_size; ++ i ) {
+								pixel_data[ shift + i ] = data[ offset ++ ];
 
-							pixels[ i ] = data[ offset ++ ];
+							}
+
+							shift += count;
 
 						}
 
-						// copy pixel array
+					}
 
-						for ( i = 0; i < count; ++ i ) {
+				} else {
 
-							pixel_data.set( pixels, shift + i * pixel_size );
+					// raw pixels
+					pixel_data = data.subarray( offset, offset += use_pal ? header.width * header.height : pixel_total );
 
-						}
+				}
 
-						shift += pixel_size * count;
+				return {
+					pixel_data: pixel_data,
+					palettes: palettes
+				};
 
-					} else {
+			}
 
-						// raw pixels
+			function tgaGetImageData8bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image, palettes ) {
 
-						count *= pixel_size;
+				var colormap = palettes;
+				var color,
+					i = 0,
+					x,
+					y;
+				var width = header.width;
 
-						for ( i = 0; i < count; ++ i ) {
+				for ( y = y_start; y !== y_end; y += y_step ) {
 
-							pixel_data[ shift + i ] = data[ offset ++ ];
+					for ( x = x_start; x !== x_end; x += x_step, i ++ ) {
 
-						}
-
-						shift += count;
+						color = image[ i ];
+						imageData[ ( x + width * y ) * 4 + 3 ] = 255;
+						imageData[ ( x + width * y ) * 4 + 2 ] = colormap[ color * 3 + 0 ];
+						imageData[ ( x + width * y ) * 4 + 1 ] = colormap[ color * 3 + 1 ];
+						imageData[ ( x + width * y ) * 4 + 0 ] = colormap[ color * 3 + 2 ];
 
 					}
 
 				}
 
-			 } else {
-
-				// raw pixels
+				return imageData;
 
-				pixel_data = data.subarray(
-					 offset, offset += ( use_pal ? header.width * header.height : pixel_total )
-				);
+			}
 
-			 }
+			function tgaGetImageData16bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
 
-			 return {
-				pixel_data: pixel_data,
-				palettes: palettes
-			 };
+				var color,
+					i = 0,
+					x,
+					y;
+				var width = header.width;
 
-		}
+				for ( y = y_start; y !== y_end; y += y_step ) {
 
-		function tgaGetImageData8bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image, palettes ) {
+					for ( x = x_start; x !== x_end; x += x_step, i += 2 ) {
 
-			var colormap = palettes;
-			var color, i = 0, x, y;
-			var width = header.width;
+						color = image[ i + 0 ] + ( image[ i + 1 ] << 8 ); // Inversed ?
 
-			for ( y = y_start; y !== y_end; y += y_step ) {
+						imageData[ ( x + width * y ) * 4 + 0 ] = ( color & 0x7C00 ) >> 7;
+						imageData[ ( x + width * y ) * 4 + 1 ] = ( color & 0x03E0 ) >> 2;
+						imageData[ ( x + width * y ) * 4 + 2 ] = ( color & 0x001F ) >> 3;
+						imageData[ ( x + width * y ) * 4 + 3 ] = color & 0x8000 ? 0 : 255;
 
-				for ( x = x_start; x !== x_end; x += x_step, i ++ ) {
-
-					color = image[ i ];
-					imageData[ ( x + width * y ) * 4 + 3 ] = 255;
-					imageData[ ( x + width * y ) * 4 + 2 ] = colormap[ ( color * 3 ) + 0 ];
-					imageData[ ( x + width * y ) * 4 + 1 ] = colormap[ ( color * 3 ) + 1 ];
-					imageData[ ( x + width * y ) * 4 + 0 ] = colormap[ ( color * 3 ) + 2 ];
+					}
 
 				}
 
-			}
+				return imageData;
 
-			return imageData;
+			}
 
-		}
+			function tgaGetImageData24bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
 
-		function tgaGetImageData16bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
+				var i = 0,
+					x,
+					y;
+				var width = header.width;
 
-			var color, i = 0, x, y;
-			var width = header.width;
+				for ( y = y_start; y !== y_end; y += y_step ) {
 
-			for ( y = y_start; y !== y_end; y += y_step ) {
+					for ( x = x_start; x !== x_end; x += x_step, i += 3 ) {
 
-				for ( x = x_start; x !== x_end; x += x_step, i += 2 ) {
+						imageData[ ( x + width * y ) * 4 + 3 ] = 255;
+						imageData[ ( x + width * y ) * 4 + 2 ] = image[ i + 0 ];
+						imageData[ ( x + width * y ) * 4 + 1 ] = image[ i + 1 ];
+						imageData[ ( x + width * y ) * 4 + 0 ] = image[ i + 2 ];
 
-					color = image[ i + 0 ] + ( image[ i + 1 ] << 8 ); // Inversed ?
-					imageData[ ( x + width * y ) * 4 + 0 ] = ( color & 0x7C00 ) >> 7;
-					imageData[ ( x + width * y ) * 4 + 1 ] = ( color & 0x03E0 ) >> 2;
-					imageData[ ( x + width * y ) * 4 + 2 ] = ( color & 0x001F ) >> 3;
-					imageData[ ( x + width * y ) * 4 + 3 ] = ( color & 0x8000 ) ? 0 : 255;
+					}
 
 				}
 
-			}
+				return imageData;
 
-			return imageData;
+			}
 
-		}
+			function tgaGetImageData32bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
 
-		function tgaGetImageData24bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
+				var i = 0,
+					x,
+					y;
+				var width = header.width;
 
-			var i = 0, x, y;
-			var width = header.width;
+				for ( y = y_start; y !== y_end; y += y_step ) {
 
-			for ( y = y_start; y !== y_end; y += y_step ) {
+					for ( x = x_start; x !== x_end; x += x_step, i += 4 ) {
 
-				for ( x = x_start; x !== x_end; x += x_step, i += 3 ) {
+						imageData[ ( x + width * y ) * 4 + 2 ] = image[ i + 0 ];
+						imageData[ ( x + width * y ) * 4 + 1 ] = image[ i + 1 ];
+						imageData[ ( x + width * y ) * 4 + 0 ] = image[ i + 2 ];
+						imageData[ ( x + width * y ) * 4 + 3 ] = image[ i + 3 ];
 
-					imageData[ ( x + width * y ) * 4 + 3 ] = 255;
-					imageData[ ( x + width * y ) * 4 + 2 ] = image[ i + 0 ];
-					imageData[ ( x + width * y ) * 4 + 1 ] = image[ i + 1 ];
-					imageData[ ( x + width * y ) * 4 + 0 ] = image[ i + 2 ];
+					}
 
 				}
 
-			}
+				return imageData;
 
-			return imageData;
+			}
 
-		}
+			function tgaGetImageDataGrey8bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
 
-		function tgaGetImageData32bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
+				var color,
+					i = 0,
+					x,
+					y;
+				var width = header.width;
 
-			var i = 0, x, y;
-			var width = header.width;
+				for ( y = y_start; y !== y_end; y += y_step ) {
 
-			for ( y = y_start; y !== y_end; y += y_step ) {
+					for ( x = x_start; x !== x_end; x += x_step, i ++ ) {
 
-				for ( x = x_start; x !== x_end; x += x_step, i += 4 ) {
+						color = image[ i ];
+						imageData[ ( x + width * y ) * 4 + 0 ] = color;
+						imageData[ ( x + width * y ) * 4 + 1 ] = color;
+						imageData[ ( x + width * y ) * 4 + 2 ] = color;
+						imageData[ ( x + width * y ) * 4 + 3 ] = 255;
 
-					imageData[ ( x + width * y ) * 4 + 2 ] = image[ i + 0 ];
-					imageData[ ( x + width * y ) * 4 + 1 ] = image[ i + 1 ];
-					imageData[ ( x + width * y ) * 4 + 0 ] = image[ i + 2 ];
-					imageData[ ( x + width * y ) * 4 + 3 ] = image[ i + 3 ];
+					}
 
 				}
 
+				return imageData;
+
 			}
 
-			return imageData;
+			function tgaGetImageDataGrey16bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
 
-		}
+				var i = 0,
+					x,
+					y;
+				var width = header.width;
 
-		function tgaGetImageDataGrey8bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
+				for ( y = y_start; y !== y_end; y += y_step ) {
 
-			var color, i = 0, x, y;
-			var width = header.width;
+					for ( x = x_start; x !== x_end; x += x_step, i += 2 ) {
 
-			for ( y = y_start; y !== y_end; y += y_step ) {
+						imageData[ ( x + width * y ) * 4 + 0 ] = image[ i + 0 ];
+						imageData[ ( x + width * y ) * 4 + 1 ] = image[ i + 0 ];
+						imageData[ ( x + width * y ) * 4 + 2 ] = image[ i + 0 ];
+						imageData[ ( x + width * y ) * 4 + 3 ] = image[ i + 1 ];
 
-				for ( x = x_start; x !== x_end; x += x_step, i ++ ) {
-
-					color = image[ i ];
-					imageData[ ( x + width * y ) * 4 + 0 ] = color;
-					imageData[ ( x + width * y ) * 4 + 1 ] = color;
-					imageData[ ( x + width * y ) * 4 + 2 ] = color;
-					imageData[ ( x + width * y ) * 4 + 3 ] = 255;
+					}
 
 				}
 
+				return imageData;
+
 			}
 
-			return imageData;
+			function getTgaRGBA( data, width, height, image, palette ) {
 
-		}
+				var x_start, y_start, x_step, y_step, x_end, y_end;
 
-		function tgaGetImageDataGrey16bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
+				switch ( ( header.flags & TGA_ORIGIN_MASK ) >> TGA_ORIGIN_SHIFT ) {
 
-			var i = 0, x, y;
-			var width = header.width;
+					default:
+					case TGA_ORIGIN_UL:
+						x_start = 0;
+						x_step = 1;
+						x_end = width;
+						y_start = 0;
+						y_step = 1;
+						y_end = height;
+						break;
 
-			for ( y = y_start; y !== y_end; y += y_step ) {
+					case TGA_ORIGIN_BL:
+						x_start = 0;
+						x_step = 1;
+						x_end = width;
+						y_start = height - 1;
+						y_step = - 1;
+						y_end = - 1;
+						break;
 
-				for ( x = x_start; x !== x_end; x += x_step, i += 2 ) {
+					case TGA_ORIGIN_UR:
+						x_start = width - 1;
+						x_step = - 1;
+						x_end = - 1;
+						y_start = 0;
+						y_step = 1;
+						y_end = height;
+						break;
 
-					imageData[ ( x + width * y ) * 4 + 0 ] = image[ i + 0 ];
-					imageData[ ( x + width * y ) * 4 + 1 ] = image[ i + 0 ];
-					imageData[ ( x + width * y ) * 4 + 2 ] = image[ i + 0 ];
-					imageData[ ( x + width * y ) * 4 + 3 ] = image[ i + 1 ];
+					case TGA_ORIGIN_BR:
+						x_start = width - 1;
+						x_step = - 1;
+						x_end = - 1;
+						y_start = height - 1;
+						y_step = - 1;
+						y_end = - 1;
+						break;
 
 				}
 
-			}
-
-			return imageData;
-
-		}
-
-		function getTgaRGBA( data, width, height, image, palette ) {
-
-			var x_start,
-				y_start,
-				x_step,
-				y_step,
-				x_end,
-				y_end;
-
-			switch ( ( header.flags & TGA_ORIGIN_MASK ) >> TGA_ORIGIN_SHIFT ) {
-
-				default:
-				case TGA_ORIGIN_UL:
-					x_start = 0;
-					x_step = 1;
-					x_end = width;
-					y_start = 0;
-					y_step = 1;
-					y_end = height;
-					break;
-
-				case TGA_ORIGIN_BL:
-					x_start = 0;
-					x_step = 1;
-					x_end = width;
-					y_start = height - 1;
-					y_step = - 1;
-					y_end = - 1;
-					break;
-
-				case TGA_ORIGIN_UR:
-					x_start = width - 1;
-					x_step = - 1;
-					x_end = - 1;
-					y_start = 0;
-					y_step = 1;
-					y_end = height;
-					break;
+				if ( use_grey ) {
 
-				case TGA_ORIGIN_BR:
-					x_start = width - 1;
-					x_step = - 1;
-					x_end = - 1;
-					y_start = height - 1;
-					y_step = - 1;
-					y_end = - 1;
-					break;
+					switch ( header.pixel_size ) {
 
-			}
+						case 8:
+							tgaGetImageDataGrey8bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
+							break;
 
-			if ( use_grey ) {
+						case 16:
+							tgaGetImageDataGrey16bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
+							break;
 
-				switch ( header.pixel_size ) {
+						default:
+							console.error( 'THREE.TGALoader: Format not supported.' );
+							break;
 
-					case 8:
-						tgaGetImageDataGrey8bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
-						break;
+					}
 
-					case 16:
-						tgaGetImageDataGrey16bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
-						break;
+				} else {
 
-					default:
-						console.error( 'THREE.TGALoader: Format not supported.' );
-						break;
+					switch ( header.pixel_size ) {
 
-				}
+						case 8:
+							tgaGetImageData8bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image, palette );
+							break;
 
-			} else {
+						case 16:
+							tgaGetImageData16bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
+							break;
 
-				switch ( header.pixel_size ) {
+						case 24:
+							tgaGetImageData24bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
+							break;
 
-					case 8:
-						tgaGetImageData8bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image, palette );
-						break;
+						case 32:
+							tgaGetImageData32bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
+							break;
 
-					case 16:
-						tgaGetImageData16bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
-						break;
+						default:
+							console.error( 'THREE.TGALoader: Format not supported.' );
+							break;
 
-					case 24:
-						tgaGetImageData24bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
-						break;
+					}
 
-					case 32:
-						tgaGetImageData32bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
-						break;
+				} // Load image data according to specific method
+				// var func = 'tgaGetImageData' + (use_grey ? 'Grey' : '') + (header.pixel_size) + 'bits';
+				// func(data, y_start, y_step, y_end, x_start, x_step, x_end, width, image, palette );
 
-					default:
-						console.error( 'THREE.TGALoader: Format not supported.' );
-						break;
 
-				}
+				return data;
 
-			}
+			} // TGA constants
 
-			// Load image data according to specific method
-			// var func = 'tgaGetImageData' + (use_grey ? 'Grey' : '') + (header.pixel_size) + 'bits';
-			// func(data, y_start, y_step, y_end, x_start, x_step, x_end, width, image, palette );
-			return data;
 
-		}
+			var TGA_TYPE_NO_DATA = 0,
+				TGA_TYPE_INDEXED = 1,
+				TGA_TYPE_RGB = 2,
+				TGA_TYPE_GREY = 3,
+				TGA_TYPE_RLE_INDEXED = 9,
+				TGA_TYPE_RLE_RGB = 10,
+				TGA_TYPE_RLE_GREY = 11,
+				TGA_ORIGIN_MASK = 0x30,
+				TGA_ORIGIN_SHIFT = 0x04,
+				TGA_ORIGIN_BL = 0x00,
+				TGA_ORIGIN_BR = 0x01,
+				TGA_ORIGIN_UL = 0x02,
+				TGA_ORIGIN_UR = 0x03;
+			if ( buffer.length < 19 ) console.error( 'THREE.TGALoader: Not enough data to contain header.' );
+			var content = new Uint8Array( buffer ),
+				offset = 0,
+				header = {
+					id_length: content[ offset ++ ],
+					colormap_type: content[ offset ++ ],
+					image_type: content[ offset ++ ],
+					colormap_index: content[ offset ++ ] | content[ offset ++ ] << 8,
+					colormap_length: content[ offset ++ ] | content[ offset ++ ] << 8,
+					colormap_size: content[ offset ++ ],
+					origin: [ content[ offset ++ ] | content[ offset ++ ] << 8, content[ offset ++ ] | content[ offset ++ ] << 8 ],
+					width: content[ offset ++ ] | content[ offset ++ ] << 8,
+					height: content[ offset ++ ] | content[ offset ++ ] << 8,
+					pixel_size: content[ offset ++ ],
+					flags: content[ offset ++ ]
+				}; // check tga if it is valid format
 
-		// TGA constants
-
-		var TGA_TYPE_NO_DATA = 0,
-			TGA_TYPE_INDEXED = 1,
-			TGA_TYPE_RGB = 2,
-			TGA_TYPE_GREY = 3,
-			TGA_TYPE_RLE_INDEXED = 9,
-			TGA_TYPE_RLE_RGB = 10,
-			TGA_TYPE_RLE_GREY = 11,
-
-			TGA_ORIGIN_MASK = 0x30,
-			TGA_ORIGIN_SHIFT = 0x04,
-			TGA_ORIGIN_BL = 0x00,
-			TGA_ORIGIN_BR = 0x01,
-			TGA_ORIGIN_UL = 0x02,
-			TGA_ORIGIN_UR = 0x03;
-
-		if ( buffer.length < 19 ) console.error( 'THREE.TGALoader: Not enough data to contain header.' );
-
-		var content = new Uint8Array( buffer ),
-			offset = 0,
-			header = {
-				id_length: content[ offset ++ ],
-				colormap_type: content[ offset ++ ],
-				image_type: content[ offset ++ ],
-				colormap_index: content[ offset ++ ] | content[ offset ++ ] << 8,
-				colormap_length: content[ offset ++ ] | content[ offset ++ ] << 8,
-				colormap_size: content[ offset ++ ],
-				origin: [
-					content[ offset ++ ] | content[ offset ++ ] << 8,
-					content[ offset ++ ] | content[ offset ++ ] << 8
-				],
-				width: content[ offset ++ ] | content[ offset ++ ] << 8,
-				height: content[ offset ++ ] | content[ offset ++ ] << 8,
-				pixel_size: content[ offset ++ ],
-				flags: content[ offset ++ ]
-			};
+			tgaCheckHeader( header );
 
-		// check tga if it is valid format
+			if ( header.id_length + offset > buffer.length ) {
 
-		tgaCheckHeader( header );
+				console.error( 'THREE.TGALoader: No data.' );
 
-		if ( header.id_length + offset > buffer.length ) {
+			} // skip the needn't data
 
-			console.error( 'THREE.TGALoader: No data.' );
 
-		}
+			offset += header.id_length; // get targa information about RLE compression and palette
 
-		// skip the needn't data
+			var use_rle = false,
+				use_pal = false,
+				use_grey = false;
 
-		offset += header.id_length;
+			switch ( header.image_type ) {
 
-		// get targa information about RLE compression and palette
+				case TGA_TYPE_RLE_INDEXED:
+					use_rle = true;
+					use_pal = true;
+					break;
 
-		var use_rle = false,
-			use_pal = false,
-			use_grey = false;
+				case TGA_TYPE_INDEXED:
+					use_pal = true;
+					break;
 
-		switch ( header.image_type ) {
+				case TGA_TYPE_RLE_RGB:
+					use_rle = true;
+					break;
 
-			case TGA_TYPE_RLE_INDEXED:
-				use_rle = true;
-				use_pal = true;
-				break;
+				case TGA_TYPE_RGB:
+					break;
 
-			case TGA_TYPE_INDEXED:
-				use_pal = true;
-				break;
+				case TGA_TYPE_RLE_GREY:
+					use_rle = true;
+					use_grey = true;
+					break;
 
-			case TGA_TYPE_RLE_RGB:
-				use_rle = true;
-				break;
+				case TGA_TYPE_GREY:
+					use_grey = true;
+					break;
 
-			case TGA_TYPE_RGB:
-				break;
+			} //
 
-			case TGA_TYPE_RLE_GREY:
-				use_rle = true;
-				use_grey = true;
-				break;
 
-			case TGA_TYPE_GREY:
-				use_grey = true;
-				break;
+			var imageData = new Uint8Array( header.width * header.height * 4 );
+			var result = tgaParse( use_rle, use_pal, header, offset, content );
+			getTgaRGBA( imageData, header.width, header.height, result.pixel_data, result.palettes );
+			return {
+				data: imageData,
+				width: header.width,
+				height: header.height,
+				flipY: true,
+				generateMipmaps: true,
+				minFilter: THREE.LinearMipmapLinearFilter
+			};
 
 		}
+	} );
 
-		//
-
-		var imageData = new Uint8Array( header.width * header.height * 4 );
-		var result = tgaParse( use_rle, use_pal, header, offset, content );
-		getTgaRGBA( imageData, header.width, header.height, result.pixel_data, result.palettes );
-
-		return {
-
-			data: imageData,
-			width: header.width,
-			height: header.height,
-			flipY: true,
-			generateMipmaps: true,
-			minFilter: THREE.LinearMipmapLinearFilter,
-
-		};
-
-	}
+	THREE.TGALoader = TGALoader;
 
-} );
+} )();

Niektoré súbory nie sú zobrazené, pretože je v týchto rozdielových dátach zmenené mnoho súborov