2
0
Mr.doob 4 жил өмнө
parent
commit
e2c5762223
100 өөрчлөгдсөн 17410 нэмэгдсэн , 13019 устгасан
  1. 395 308
      build/three.js
  2. 0 0
      build/three.min.js
  3. 300 298
      build/three.module.js
  4. 5 0
      docs/api/en/helpers/AxesHelper.html
  5. 4 0
      docs/api/en/helpers/CameraHelper.html
  6. 6 0
      docs/api/en/lights/DirectionalLight.html
  7. 4 0
      docs/api/en/lights/Light.html
  8. 6 0
      docs/api/en/lights/PointLight.html
  9. 6 0
      docs/api/en/lights/SpotLight.html
  10. 5 0
      docs/api/en/lights/shadows/LightShadow.html
  11. 1 1
      docs/api/en/math/Color.html
  12. 5 0
      docs/api/en/objects/SkinnedMesh.html
  13. 2 2
      docs/api/en/renderers/WebGLRenderer.html
  14. 1 1
      docs/api/zh/math/Color.html
  15. 1 1
      docs/examples/en/controls/TrackballControls.html
  16. 19 0
      docs/manual/en/introduction/How-to-run-things-locally.html
  17. 2 2
      editor/js/Editor.js
  18. 1 1
      editor/js/Loader.js
  19. 1 1
      editor/js/Menubar.File.js
  20. 1 1
      editor/js/Script.js
  21. 2 1
      editor/js/Sidebar.Project.Renderer.js
  22. 10 0
      editor/js/Sidebar.Project.js
  23. 10 46
      editor/js/Viewport.VR.js
  24. 206 208
      editor/js/Viewport.ViewHelper.js
  25. 1 1
      editor/js/Viewport.js
  26. 6 2
      editor/sw.js
  27. 2 1
      examples/files.json
  28. 1 1
      examples/index.html
  29. 56 55
      examples/js/WebGL.js
  30. 62 71
      examples/js/animation/AnimationClipCreator.js
  31. 231 265
      examples/js/animation/CCDIKSolver.js
  32. 231 398
      examples/js/animation/MMDAnimationHelper.js
  33. 268 340
      examples/js/animation/MMDPhysics.js
  34. 114 152
      examples/js/cameras/CinematicCamera.js
  35. 80 88
      examples/js/controls/DeviceOrientationControls.js
  36. 220 179
      examples/js/controls/DragControls.js
  37. 219 217
      examples/js/controls/FirstPersonControls.js
  38. 247 188
      examples/js/controls/FlyControls.js
  39. 654 824
      examples/js/controls/OrbitControls.js
  40. 94 90
      examples/js/controls/PointerLockControls.js
  41. 466 482
      examples/js/controls/TrackballControls.js
  42. 869 1061
      examples/js/controls/TransformControls.js
  43. 1044 0
      examples/js/controls/experimental/CameraControls.js
  44. 382 0
      examples/js/csm/CSM.js
  45. 145 0
      examples/js/csm/CSMHelper.js
  46. 241 0
      examples/js/csm/CSMShader.js
  47. 133 0
      examples/js/csm/Frustum.js
  48. 200 270
      examples/js/curves/CurveExtras.js
  49. 47 38
      examples/js/curves/NURBSCurve.js
  50. 33 25
      examples/js/curves/NURBSSurface.js
  51. 272 289
      examples/js/curves/NURBSUtils.js
  52. 1656 0
      examples/js/deprecated/Geometry.js
  53. 68 122
      examples/js/effects/AnaglyphEffect.js
  54. 184 187
      examples/js/effects/AsciiEffect.js
  55. 275 363
      examples/js/effects/OutlineEffect.js
  56. 50 72
      examples/js/effects/ParallaxBarrierEffect.js
  57. 111 87
      examples/js/effects/PeppersGhostEffect.js
  58. 32 26
      examples/js/effects/StereoEffect.js
  59. 53 0
      examples/js/environments/DebugEnvironment.js
  60. 100 0
      examples/js/environments/RoomEnvironment.js
  61. 279 475
      examples/js/exporters/ColladaExporter.js
  62. 115 139
      examples/js/exporters/DracoExporter.js
  63. 293 354
      examples/js/exporters/GLTFExporter.js
  64. 120 134
      examples/js/exporters/MMDExporter.js
  65. 147 173
      examples/js/exporters/OBJExporter.js
  66. 267 351
      examples/js/exporters/PLYExporter.js
  67. 120 126
      examples/js/exporters/STLExporter.js
  68. 386 0
      examples/js/exporters/USDZExporter.js
  69. 42 45
      examples/js/geometries/BoxLineGeometry.js
  70. 26 30
      examples/js/geometries/ConvexGeometry.js
  71. 187 237
      examples/js/geometries/DecalGeometry.js
  72. 560 692
      examples/js/geometries/LightningStrike.js
  73. 141 178
      examples/js/geometries/ParametricGeometries.js
  74. 140 0
      examples/js/geometries/RoundedBoxGeometry.js
  75. 21 454
      examples/js/geometries/TeapotGeometry.js
  76. 49 0
      examples/js/helpers/LightProbeHelper.js
  77. 89 0
      examples/js/helpers/PositionalAudioHelper.js
  78. 73 0
      examples/js/helpers/RectAreaLightHelper.js
  79. 91 0
      examples/js/helpers/VertexNormalsHelper.js
  80. 72 0
      examples/js/helpers/VertexTangentsHelper.js
  81. 291 0
      examples/js/interactive/HTMLMesh.js
  82. 100 0
      examples/js/interactive/InteractiveGroup.js
  83. 181 143
      examples/js/interactive/SelectionBox.js
  84. 49 54
      examples/js/interactive/SelectionHelper.js
  85. 0 1
      examples/js/libs/chevrotain.min.js
  86. 1 0
      examples/js/libs/fflate.min.js
  87. 142 153
      examples/js/lights/LightProbeGenerator.js
  88. 6 11
      examples/js/lights/RectAreaLightUniformsLib.js
  89. 12 10
      examples/js/lines/Line2.js
  90. 51 56
      examples/js/lines/LineGeometry.js
  91. 138 181
      examples/js/lines/LineMaterial.js
  92. 166 163
      examples/js/lines/LineSegments2.js
  93. 104 122
      examples/js/lines/LineSegmentsGeometry.js
  94. 29 28
      examples/js/lines/Wireframe.js
  95. 11 11
      examples/js/lines/WireframeGeometry2.js
  96. 1283 0
      examples/js/loaders/3DMLoader.js
  97. 798 908
      examples/js/loaders/3MFLoader.js
  98. 290 266
      examples/js/loaders/AMFLoader.js
  99. 237 268
      examples/js/loaders/BVHLoader.js
  100. 463 491
      examples/js/loaders/BasisTextureLoader.js

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 395 - 308
build/three.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 0
build/three.min.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 300 - 298
build/three.module.js


+ 5 - 0
docs/api/en/helpers/AxesHelper.html

@@ -44,6 +44,11 @@ scene.add( axesHelper );
 		<h2>Methods</h2>
 		<p>See the base [page:LineSegments] class for common methods.</p>
 
+		<h3>[method:AxesHelper dispose]()</h3>
+		<p>
+		Disposes of the internally-created [page:Line.material material] and [page:Line.geometry geometry] used by this helper.
+		</p>
+
 		<h2>Source</h2>
 
 		<p>

+ 4 - 0
docs/api/en/helpers/CameraHelper.html

@@ -68,6 +68,10 @@ scene.add( helper );
 		<h2>Methods</h2>
 		<p>See the base [page:LineSegments] class for common methods.</p>
 
+		<h3>[method:CameraHelper dispose]()</h3>
+		<p>
+		Disposes of the internally-created [page:Line.material material] and [page:Line.geometry geometry] used by this helper.
+		</p>
 
 		<h3>[method:null update]()</h3>
 		<p>Updates the helper based on the projectionMatrix of the camera.</p>

+ 6 - 0
docs/api/en/lights/DirectionalLight.html

@@ -117,6 +117,12 @@
 
 		<p>See the base [page:Light Light] class for common methods.</p>
 
+		<h3>[method:DirectionalLight dispose]()</h3>
+		<p>
+		Override of base class's [page:Light.dispose dispose].
+		Disposes of this light's [page:DirectionalLightShadow shadow].
+		</p>
+
 		<h3>[method:DirectionalLight copy]( [param:DirectionalLight source] )</h3>
 		<p>
 		Copies value of all the properties from the [page:DirectionalLight source] to this

+ 4 - 0
docs/api/en/lights/Light.html

@@ -52,6 +52,10 @@
 			See the base [page:Object3D Object3D] class for common methods.
 		</p>
 
+		<h3>[method:Light dispose]()</h3>
+		<p>
+		Abstract dispose method for lights; implemented by subclasses that have disposable resources.
+		</p>
 
 		<h3>[method:Light copy]( [param:Light source] )</h3>
 		<p>

+ 6 - 0
docs/api/en/lights/PointLight.html

@@ -105,6 +105,12 @@ scene.add( light );
 			See the base [page:Light Light] class for common methods.
 		</p>
 
+		<h3>[method:PointLight dispose]()</h3>
+		<p>
+		Override of base class's [page:Light.dispose dispose].
+		Disposes of this light's [page:PointLightShadow shadow].
+		</p>
+
 		<h3>[method:PointLight copy]( [param:PointLight source] )</h3>
 		<p>
 		Copies value of all the properties from the [page:PointLight source] to this

+ 6 - 0
docs/api/en/lights/SpotLight.html

@@ -163,6 +163,12 @@ light.target = targetObject;
 
 		<p>See the base [page:Light Light] class for common methods.</p>
 
+		<h3>[method:SpotLight dispose]()</h3>
+		<p>
+		Override of base class's [page:Light.dispose dispose].
+		Disposes of this light's [page:SpotLightShadow shadow].
+		</p>
+
 		<h3>[method:SpotLight copy]( [param:SpotLight source] )</h3>
 		<p>
 		Copies value of all the properties from the [page:SpotLight source] to this

+ 5 - 0
docs/api/en/lights/shadows/LightShadow.html

@@ -122,6 +122,11 @@
 		Used internally by the renderer to get the number of viewports that need to be rendered for this shadow.
 		</p>
 
+		<h3>[method:LightShadow dispose]()</h3>
+		<p>
+		Disposes of this shadow's textures ([page:LightShadow.map map] and [page:LightShadow.mapPass mapPass]).
+		</p>
+
 		<h3>[method:LightShadow copy]( [param:LightShadow source] )</h3>
 		<p>
 		Copies value of all the properties from the [page:LightShadow source] to this

+ 1 - 1
docs/api/en/math/Color.html

@@ -310,7 +310,7 @@ const color7 = new THREE.Color( 1, 0, 0 );
 		Translucent colors such as "rgba(255, 0, 0, 0.5)" and "hsla(0, 100%, 50%, 0.5)" are also accepted,
 		but the alpha-channel coordinate will be discarded.<br /><br />
 
-		Note that for X11 color names, multiple words such as Dark Orange become the string 'darkorange' (all lowercase).
+		Note that for X11 color names, multiple words such as Dark Orange become the string 'darkorange'.
 		</p>
 
 		<h3>[method:Color setColorName]( [param:String style] ) </h3>

+ 5 - 0
docs/api/en/objects/SkinnedMesh.html

@@ -153,6 +153,11 @@
 		Updates the [page:Matrix4 MatrixWorld].
 		</p>
 
+		<h3>[method:Vector3 boneTransform]( [index:Integer], [target:Vector3] )</h3>
+		<p>
+		Calculates the position of the vertex at the given index relative to the current bone transformations.
+		</p>
+			
 		<h2>Source</h2>
 
 		<p>

+ 2 - 2
docs/api/en/renderers/WebGLRenderer.html

@@ -298,8 +298,8 @@
 		<h3>[method:null copyTextureToTexture]( [param:Vector2 position], [param:Texture srcTexture], [param:Texture dstTexture], [param:Number level] )</h3>
 		<p>Copies all pixels of a texture to an existing texture starting from the given position. Enables access to [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGLRenderingContext/texSubImage2D WebGLRenderingContext.texSubImage2D].</p>
 
-		<h3>[method:null copyTextureToTexture3D]( [param:Box3 sourceBox], [param:Vector2 position], [param:Texture srcTexture], [param:Texture dstTexture], [param:Number level] )</h3>
-		<p>Copies the pixels of a texture in the bounds '[page:Box3 sourceBox]' in the desination texture starting from the given position. Enables access to [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/texSubImage3D WebGL2RenderingContext.texSubImage3D].</p>
+		<h3>[method:null copyTextureToTexture3D]( [param:Box3 sourceBox], [param:Vector3 position], [param:Texture srcTexture], [param:Texture dstTexture], [param:Number level] )</h3>
+		<p>Copies the pixels of a texture in the bounds '[page:Box3 sourceBox]' in the destination texture starting from the given position. Enables access to [link:https://developer.mozilla.org/en-US/docs/Web/API/WebGL2RenderingContext/texSubImage3D WebGL2RenderingContext.texSubImage3D].</p>
 
 		<h3>[method:null dispose]( )</h3>
 		<p>Dispose of the current rendering context.</p>

+ 1 - 1
docs/api/zh/math/Color.html

@@ -303,7 +303,7 @@
 		半透明颜色例如 "rgba(255, 0, 0, 0.5)" and "hsla(0, 100%, 50%, 0.5)" 也能支持,
 		但是alpha通道的值将会被丢弃。<br /><br />
 
-		注意,对于X11颜色名称,多个单词(如暗橙色)变成字符串“darkorange”(全部是小写字母)
+		注意,对于X11颜色名称,多个单词(如暗橙色)变成字符串“darkorange”。
 		</p>
 
 		<h3>[method:Color setColorName]( [param:String style] ) </h3>

+ 1 - 1
docs/examples/en/controls/TrackballControls.html

@@ -126,7 +126,7 @@
 
 		<h3>[property:Number panSpeed]</h3>
 		<p>
-			The zoom speed. Default is *0.3*.
+			The pan speed. Default is *0.3*.
 		</p>
 
 		<h3>[property:Number rotateSpeed]</h3>

+ 19 - 0
docs/manual/en/introduction/How-to-run-things-locally.html

@@ -52,6 +52,7 @@
 			<div>
 				<p>Some code editors have plugins which will spawn a simple server on demand.</p>
 				<ul>
+					<li>[link:https://marketplace.visualstudio.com/items?itemName=yandeu.five-server Five Server] for Visual Studio Code.</li>
 					<li>[link:https://marketplace.visualstudio.com/items?itemName=ritwickdey.LiveServer Live Server] for Visual Studio Code.</li>
 					<li>[link:https://atom.io/packages/atom-live-server Live Server] for Atom.</li>
 				</ul>
@@ -64,6 +65,24 @@
 				</p>
 			</div>
 
+			<h3>Node.js five-server</h3>
+			<div>
+				<p>Development server with live reload capability. To install:</p>
+				<code>
+# Remove live-server (if you have it)
+npm -g rm live-server
+
+# Install five-server
+npm -g i five-server
+
+# Update five-server (from time to time)
+npm -g i five-server@latest
+				</code>
+
+				<p>To run (from your local directory):</p>
+				<code>five-server . -p 8000</code>
+			</div>
+
 			<h3>Node.js http-server</h3>
 			<div>
 				<p>Node.js has a simple HTTP server package. To install:</p>

+ 2 - 2
editor/js/Editor.js

@@ -41,7 +41,7 @@ function Editor() {
 		transformModeChanged: new Signal(),
 		snapChanged: new Signal(),
 		spaceChanged: new Signal(),
-		rendererChanged: new Signal(),
+		rendererCreated: new Signal(),
 		rendererUpdated: new Signal(),
 
 		sceneBackgroundChanged: new Signal(),
@@ -414,7 +414,7 @@ Editor.prototype = {
 
 				} else if ( object.isSpotLight ) {
 
-					helper = new THREE.SpotLightHelper( object, 1 );
+					helper = new THREE.SpotLightHelper( object );
 
 				} else if ( object.isHemisphereLight ) {
 

+ 1 - 1
editor/js/Loader.js

@@ -7,7 +7,7 @@ import { SetSceneCommand } from './commands/SetSceneCommand.js';
 
 import { LoaderUtils } from './LoaderUtils.js';
 
-import { unzipSync, strFromU8 } from '../../examples/jsm/libs/fflate.module.min.js';
+import { unzipSync, strFromU8 } from '../../examples/jsm/libs/fflate.module.js';
 
 function Loader( editor ) {
 

+ 1 - 1
editor/js/Menubar.File.js

@@ -1,6 +1,6 @@
 import * as THREE from '../../build/three.module.js';
 
-import { zipSync, strToU8 } from '../../examples/jsm/libs/fflate.module.min.js';
+import { zipSync, strToU8 } from '../../examples/jsm/libs/fflate.module.js';
 
 import { UIPanel, UIRow, UIHorizontalRule } from './libs/ui.js';
 

+ 1 - 1
editor/js/Script.js

@@ -48,7 +48,7 @@ function Script( editor ) {
 
 	var renderer;
 
-	signals.rendererChanged.add( function ( newRenderer ) {
+	signals.rendererCreated.add( function ( newRenderer ) {
 
 		renderer = newRenderer;
 

+ 2 - 1
editor/js/Sidebar.Project.Renderer.js

@@ -116,7 +116,8 @@ function SidebarProjectRenderer( editor ) {
 		currentRenderer.toneMapping = parseFloat( toneMappingSelect.getValue() );
 		currentRenderer.toneMappingExposure = toneMappingExposure.getValue();
 
-		signals.rendererChanged.dispatch( currentRenderer );
+		signals.rendererCreated.dispatch( currentRenderer );
+		signals.rendererUpdated.dispatch();
 
 	}
 

+ 10 - 0
editor/js/Sidebar.Project.js

@@ -7,6 +7,7 @@ import { SidebarProjectVideo } from './Sidebar.Project.Video.js';
 function SidebarProject( editor ) {
 
 	var config = editor.config;
+	var signals = editor.signals;
 	var strings = editor.strings;
 
 	var container = new UISpan();
@@ -69,6 +70,15 @@ function SidebarProject( editor ) {
 
 	}
 
+	// Signals
+
+	signals.editorCleared.add( function () {
+
+		title.setValue( '' );
+		config.setKey( 'project/title', '' );
+
+	} );
+
 	return container;
 
 }

+ 10 - 46
editor/js/Viewport.VR.js

@@ -1,6 +1,7 @@
 import * as THREE from '../../build/three.module.js';
 
-import { HTMLMesh } from './libs/three.html.js';
+import { HTMLMesh } from '../../examples/jsm/interactive/HTMLMesh.js';
+import { InteractiveGroup } from '../../examples/jsm/interactive/InteractiveGroup.js';
 
 import { XRControllerModelFactory } from '../../examples/jsm/webxr/XRControllerModelFactory.js';
 
@@ -29,31 +30,29 @@ class VR {
 
 			if ( group === null ) {
 
-				group = new THREE.Group();
+				group = new InteractiveGroup( renderer );
 				editor.sceneHelpers.add( group );
 
 				const mesh = new HTMLMesh( sidebar );
 				mesh.position.set( 1, 1.5, - 0.5 );
 				mesh.rotation.y = - 0.5;
+				mesh.scale.setScalar( 2 );
 				group.add( mesh );
 
 				intersectables.push( mesh );
 
 				// controllers
 
-				const controller1 = renderer.xr.getController( 0 );
-				controller1.addEventListener( 'select', onSelect );
-				group.add( controller1 );
-
-				const controller2 = renderer.xr.getController( 1 );
-				controller2.addEventListener( 'selectstart', onSelect );
-				group.add( controller2 );
-
 				const geometry = new THREE.BufferGeometry();
 				geometry.setFromPoints( [ new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 0, 0, - 5 ) ] );
 
+				const controller1 = renderer.xr.getController( 0 );
 				controller1.add( new THREE.Line( geometry ) );
+				group.add( controller1 );
+
+				const controller2 = renderer.xr.getController( 1 );
 				controller2.add( new THREE.Line( geometry ) );
+				group.add( controller2 );
 
 				//
 
@@ -101,41 +100,6 @@ class VR {
 
 		};
 
-		//
-
-		function onSelect( event ) {
-
-			const controller = event.target;
-
-			const intersections = getIntersections( controller );
-
-			if ( intersections.length > 0 ) {
-
-				const intersection = intersections[ 0 ];
-
-				const object = intersection.object;
-				const uv = intersection.uv;
-
-				object.material.map.click( uv.x, 1 - uv.y );
-
-			}
-
-		}
-
-		const raycaster = new THREE.Raycaster();
-		const tempMatrix = new THREE.Matrix4();
-
-		function getIntersections( controller ) {
-
-			tempMatrix.identity().extractRotation( controller.matrixWorld );
-
-			raycaster.ray.origin.setFromMatrixPosition( controller.matrixWorld );
-			raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( tempMatrix );
-
-			return raycaster.intersectObjects( intersectables );
-
-		}
-
 		// signals
 
 		signals.toggleVR.add( () => {
@@ -153,7 +117,7 @@ class VR {
 
 		} );
 
-		signals.rendererChanged.add( ( value ) => {
+		signals.rendererCreated.add( ( value ) => {
 
 			renderer = value;
 			renderer.xr.enabled = true;

+ 206 - 208
editor/js/Viewport.ViewHelper.js

@@ -2,317 +2,315 @@ import { UIPanel } from './libs/ui.js';
 
 import * as THREE from '../../build/three.module.js';
 
-function ViewHelper( editorCamera, container ) {
+class ViewHelper extends THREE.Object3D {
 
-	THREE.Object3D.call( this );
+	constructor( editorCamera, container ) {
 
-	this.animating = false;
-	this.controls = null;
+		super();
 
-	var panel = new UIPanel();
-	panel.setId( 'viewHelper' );
-	panel.setPosition( 'absolute' );
-	panel.setRight( '0px' );
-	panel.setBottom( '0px' );
-	panel.setHeight( '128px' );
-	panel.setWidth( '128px' );
+		this.animating = false;
+		this.controls = null;
 
-	var scope = this;
+		const panel = new UIPanel();
+		panel.setId( 'viewHelper' );
+		panel.setPosition( 'absolute' );
+		panel.setRight( '0px' );
+		panel.setBottom( '0px' );
+		panel.setHeight( '128px' );
+		panel.setWidth( '128px' );
 
-	panel.dom.addEventListener( 'mouseup', function ( event ) {
+		const scope = this;
 
-		event.stopPropagation();
+		panel.dom.addEventListener( 'mouseup', function ( event ) {
 
-		scope.handleClick( event );
+			event.stopPropagation();
 
-	} );
+			scope.handleClick( event );
 
-	panel.dom.addEventListener( 'mousedown', function ( event ) {
+		} );
 
-		event.stopPropagation();
+		panel.dom.addEventListener( 'mousedown', function ( event ) {
 
-	} );
+			event.stopPropagation();
 
-	container.add( panel );
+		} );
 
-	var color1 = new THREE.Color( '#ff3653' );
-	var color2 = new THREE.Color( '#8adb00' );
-	var color3 = new THREE.Color( '#2c8fff' );
+		container.add( panel );
 
-	var interactiveObjects = [];
-	var raycaster = new THREE.Raycaster();
-	var mouse = new THREE.Vector2();
-	var dummy = new THREE.Object3D();
+		const color1 = new THREE.Color( '#ff3653' );
+		const color2 = new THREE.Color( '#8adb00' );
+		const color3 = new THREE.Color( '#2c8fff' );
 
-	var camera = new THREE.OrthographicCamera( - 2, 2, 2, - 2, 0, 4 );
-	camera.position.set( 0, 0, 2 );
+		const interactiveObjects = [];
+		const raycaster = new THREE.Raycaster();
+		const mouse = new THREE.Vector2();
+		const dummy = new THREE.Object3D();
 
-	var geometry = new THREE.BoxGeometry( 0.8, 0.05, 0.05 ).translate( 0.4, 0, 0 );
+		const camera = new THREE.OrthographicCamera( - 2, 2, 2, - 2, 0, 4 );
+		camera.position.set( 0, 0, 2 );
 
-	var xAxis = new THREE.Mesh( geometry, getAxisMaterial( color1 ) );
-	var yAxis = new THREE.Mesh( geometry, getAxisMaterial( color2 ) );
-	var zAxis = new THREE.Mesh( geometry, getAxisMaterial( color3 ) );
+		const geometry = new THREE.BoxGeometry( 0.8, 0.05, 0.05 ).translate( 0.4, 0, 0 );
 
-	yAxis.rotation.z = Math.PI / 2;
-	zAxis.rotation.y = - Math.PI / 2;
+		const xAxis = new THREE.Mesh( geometry, getAxisMaterial( color1 ) );
+		const yAxis = new THREE.Mesh( geometry, getAxisMaterial( color2 ) );
+		const zAxis = new THREE.Mesh( geometry, getAxisMaterial( color3 ) );
 
-	this.add( xAxis );
-	this.add( zAxis );
-	this.add( yAxis );
+		yAxis.rotation.z = Math.PI / 2;
+		zAxis.rotation.y = - Math.PI / 2;
 
-	var posXAxisHelper = new THREE.Sprite( getSpriteMaterial( color1, 'X' ) );
-	posXAxisHelper.userData.type = 'posX';
-	var posYAxisHelper = new THREE.Sprite( getSpriteMaterial( color2, 'Y' ) );
-	posYAxisHelper.userData.type = 'posY';
-	var posZAxisHelper = new THREE.Sprite( getSpriteMaterial( color3, 'Z' ) );
-	posZAxisHelper.userData.type = 'posZ';
-	var negXAxisHelper = new THREE.Sprite( getSpriteMaterial( color1 ) );
-	negXAxisHelper.userData.type = 'negX';
-	var negYAxisHelper = new THREE.Sprite( getSpriteMaterial( color2 ) );
-	negYAxisHelper.userData.type = 'negY';
-	var negZAxisHelper = new THREE.Sprite( getSpriteMaterial( color3 ) );
-	negZAxisHelper.userData.type = 'negZ';
+		this.add( xAxis );
+		this.add( zAxis );
+		this.add( yAxis );
 
-	posXAxisHelper.position.x = 1;
-	posYAxisHelper.position.y = 1;
-	posZAxisHelper.position.z = 1;
-	negXAxisHelper.position.x = - 1;
-	negXAxisHelper.scale.setScalar( 0.8 );
-	negYAxisHelper.position.y = - 1;
-	negYAxisHelper.scale.setScalar( 0.8 );
-	negZAxisHelper.position.z = - 1;
-	negZAxisHelper.scale.setScalar( 0.8 );
+		const posXAxisHelper = new THREE.Sprite( getSpriteMaterial( color1, 'X' ) );
+		posXAxisHelper.userData.type = 'posX';
+		const posYAxisHelper = new THREE.Sprite( getSpriteMaterial( color2, 'Y' ) );
+		posYAxisHelper.userData.type = 'posY';
+		const posZAxisHelper = new THREE.Sprite( getSpriteMaterial( color3, 'Z' ) );
+		posZAxisHelper.userData.type = 'posZ';
+		const negXAxisHelper = new THREE.Sprite( getSpriteMaterial( color1 ) );
+		negXAxisHelper.userData.type = 'negX';
+		const negYAxisHelper = new THREE.Sprite( getSpriteMaterial( color2 ) );
+		negYAxisHelper.userData.type = 'negY';
+		const negZAxisHelper = new THREE.Sprite( getSpriteMaterial( color3 ) );
+		negZAxisHelper.userData.type = 'negZ';
 
-	this.add( posXAxisHelper );
-	this.add( posYAxisHelper );
-	this.add( posZAxisHelper );
-	this.add( negXAxisHelper );
-	this.add( negYAxisHelper );
-	this.add( negZAxisHelper );
+		posXAxisHelper.position.x = 1;
+		posYAxisHelper.position.y = 1;
+		posZAxisHelper.position.z = 1;
+		negXAxisHelper.position.x = - 1;
+		negXAxisHelper.scale.setScalar( 0.8 );
+		negYAxisHelper.position.y = - 1;
+		negYAxisHelper.scale.setScalar( 0.8 );
+		negZAxisHelper.position.z = - 1;
+		negZAxisHelper.scale.setScalar( 0.8 );
 
-	interactiveObjects.push( posXAxisHelper );
-	interactiveObjects.push( posYAxisHelper );
-	interactiveObjects.push( posZAxisHelper );
-	interactiveObjects.push( negXAxisHelper );
-	interactiveObjects.push( negYAxisHelper );
-	interactiveObjects.push( negZAxisHelper );
+		this.add( posXAxisHelper );
+		this.add( posYAxisHelper );
+		this.add( posZAxisHelper );
+		this.add( negXAxisHelper );
+		this.add( negYAxisHelper );
+		this.add( negZAxisHelper );
 
-	var point = new THREE.Vector3();
-	var dim = 128;
-	var turnRate = 2 * Math.PI; // turn rate in angles per second
+		interactiveObjects.push( posXAxisHelper );
+		interactiveObjects.push( posYAxisHelper );
+		interactiveObjects.push( posZAxisHelper );
+		interactiveObjects.push( negXAxisHelper );
+		interactiveObjects.push( negYAxisHelper );
+		interactiveObjects.push( negZAxisHelper );
 
-	this.render = function ( renderer ) {
+		const point = new THREE.Vector3();
+		const dim = 128;
+		const turnRate = 2 * Math.PI; // turn rate in angles per second
 
-		this.quaternion.copy( editorCamera.quaternion ).invert();
-		this.updateMatrixWorld();
+		this.render = function ( renderer ) {
 
-		point.set( 0, 0, 1 );
-		point.applyQuaternion( editorCamera.quaternion );
+			this.quaternion.copy( editorCamera.quaternion ).invert();
+			this.updateMatrixWorld();
 
-		if ( point.x >= 0 ) {
+			point.set( 0, 0, 1 );
+			point.applyQuaternion( editorCamera.quaternion );
 
-			posXAxisHelper.material.opacity = 1;
-			negXAxisHelper.material.opacity = 0.5;
+			if ( point.x >= 0 ) {
 
-		} else {
+				posXAxisHelper.material.opacity = 1;
+				negXAxisHelper.material.opacity = 0.5;
 
-			posXAxisHelper.material.opacity = 0.5;
-			negXAxisHelper.material.opacity = 1;
+			} else {
 
-		}
+				posXAxisHelper.material.opacity = 0.5;
+				negXAxisHelper.material.opacity = 1;
 
-		if ( point.y >= 0 ) {
+			}
 
-			posYAxisHelper.material.opacity = 1;
-			negYAxisHelper.material.opacity = 0.5;
+			if ( point.y >= 0 ) {
 
-		} else {
+				posYAxisHelper.material.opacity = 1;
+				negYAxisHelper.material.opacity = 0.5;
 
-			posYAxisHelper.material.opacity = 0.5;
-			negYAxisHelper.material.opacity = 1;
+			} else {
 
-		}
+				posYAxisHelper.material.opacity = 0.5;
+				negYAxisHelper.material.opacity = 1;
 
-		if ( point.z >= 0 ) {
+			}
 
-			posZAxisHelper.material.opacity = 1;
-			negZAxisHelper.material.opacity = 0.5;
+			if ( point.z >= 0 ) {
 
-		} else {
+				posZAxisHelper.material.opacity = 1;
+				negZAxisHelper.material.opacity = 0.5;
 
-			posZAxisHelper.material.opacity = 0.5;
-			negZAxisHelper.material.opacity = 1;
+			} else {
 
-		}
+				posZAxisHelper.material.opacity = 0.5;
+				negZAxisHelper.material.opacity = 1;
 
-		//
+			}
 
-		var x = container.dom.offsetWidth - dim;
+			//
 
-		renderer.clearDepth();
-		renderer.setViewport( x, 0, dim, dim );
-		renderer.render( this, camera );
+			const x = container.dom.offsetWidth - dim;
 
-	};
+			renderer.clearDepth();
+			renderer.setViewport( x, 0, dim, dim );
+			renderer.render( this, camera );
 
-	var targetPosition = new THREE.Vector3();
-	var targetQuaternion = new THREE.Quaternion();
+		};
 
-	var q1 = new THREE.Quaternion();
-	var q2 = new THREE.Quaternion();
-	var radius = 0;
+		const targetPosition = new THREE.Vector3();
+		const targetQuaternion = new THREE.Quaternion();
 
-	this.handleClick = function ( event ) {
+		const q1 = new THREE.Quaternion();
+		const q2 = new THREE.Quaternion();
+		let radius = 0;
 
-		if ( this.animating === true ) return false;
+		this.handleClick = function ( event ) {
 
-		var rect = container.dom.getBoundingClientRect();
-		var offsetX = rect.left + ( container.dom.offsetWidth - dim );
-		var offsetY = rect.top + ( container.dom.offsetHeight - dim );
-		mouse.x = ( ( event.clientX - offsetX ) / ( rect.width - offsetX ) ) * 2 - 1;
-		mouse.y = - ( ( event.clientY - offsetY ) / ( rect.bottom - offsetY ) ) * 2 + 1;
+			if ( this.animating === true ) return false;
 
-		raycaster.setFromCamera( mouse, camera );
+			const rect = container.dom.getBoundingClientRect();
+			const offsetX = rect.left + ( container.dom.offsetWidth - dim );
+			const offsetY = rect.top + ( container.dom.offsetHeight - dim );
+			mouse.x = ( ( event.clientX - offsetX ) / ( rect.width - offsetX ) ) * 2 - 1;
+			mouse.y = - ( ( event.clientY - offsetY ) / ( rect.bottom - offsetY ) ) * 2 + 1;
 
-		var intersects = raycaster.intersectObjects( interactiveObjects );
+			raycaster.setFromCamera( mouse, camera );
 
-		if ( intersects.length > 0 ) {
+			const intersects = raycaster.intersectObjects( interactiveObjects );
 
-			var intersection = intersects[ 0 ];
-			var object = intersection.object;
+			if ( intersects.length > 0 ) {
 
-			prepareAnimationData( object, this.controls.center );
+				const intersection = intersects[ 0 ];
+				const object = intersection.object;
 
-			this.animating = true;
+				prepareAnimationData( object, this.controls.center );
 
-			return true;
+				this.animating = true;
 
-		} else {
+				return true;
 
-			return false;
+			} else {
 
-		}
+				return false;
 
-	};
+			}
 
-	this.update = function ( delta ) {
+		};
 
-		var step = delta * turnRate;
-		var focusPoint = this.controls.center;
+		this.update = function ( delta ) {
 
-		// animate position by doing a slerp and then scaling the position on the unit sphere
+			const step = delta * turnRate;
+			const focusPoint = this.controls.center;
 
-		q1.rotateTowards( q2, step );
-		editorCamera.position.set( 0, 0, 1 ).applyQuaternion( q1 ).multiplyScalar( radius ).add( focusPoint );
+			// animate position by doing a slerp and then scaling the position on the unit sphere
 
-		// animate orientation
+			q1.rotateTowards( q2, step );
+			editorCamera.position.set( 0, 0, 1 ).applyQuaternion( q1 ).multiplyScalar( radius ).add( focusPoint );
 
-		editorCamera.quaternion.rotateTowards( targetQuaternion, step );
+			// animate orientation
 
-		if ( q1.angleTo( q2 ) === 0 ) {
+			editorCamera.quaternion.rotateTowards( targetQuaternion, step );
 
-			this.animating = false;
+			if ( q1.angleTo( q2 ) === 0 ) {
 
-		}
+				this.animating = false;
 
-	};
+			}
 
-	function prepareAnimationData( object, focusPoint ) {
+		};
 
-		switch ( object.userData.type ) {
+		function prepareAnimationData( object, focusPoint ) {
 
-			case 'posX':
-				targetPosition.set( 1, 0, 0 );
-				targetQuaternion.setFromEuler( new THREE.Euler( 0, Math.PI * 0.5, 0 ) );
-				break;
+			switch ( object.userData.type ) {
 
-			case 'posY':
-				targetPosition.set( 0, 1, 0 );
-				targetQuaternion.setFromEuler( new THREE.Euler( - Math.PI * 0.5, 0, 0 ) );
-				break;
+				case 'posX':
+					targetPosition.set( 1, 0, 0 );
+					targetQuaternion.setFromEuler( new THREE.Euler( 0, Math.PI * 0.5, 0 ) );
+					break;
 
-			case 'posZ':
-				targetPosition.set( 0, 0, 1 );
-				targetQuaternion.setFromEuler( new THREE.Euler() );
-				break;
+				case 'posY':
+					targetPosition.set( 0, 1, 0 );
+					targetQuaternion.setFromEuler( new THREE.Euler( - Math.PI * 0.5, 0, 0 ) );
+					break;
 
-			case 'negX':
-				targetPosition.set( - 1, 0, 0 );
-				targetQuaternion.setFromEuler( new THREE.Euler( 0, - Math.PI * 0.5, 0 ) );
-				break;
+				case 'posZ':
+					targetPosition.set( 0, 0, 1 );
+					targetQuaternion.setFromEuler( new THREE.Euler() );
+					break;
 
-			case 'negY':
-				targetPosition.set( 0, - 1, 0 );
-				targetQuaternion.setFromEuler( new THREE.Euler( Math.PI * 0.5, 0, 0 ) );
-				break;
+				case 'negX':
+					targetPosition.set( - 1, 0, 0 );
+					targetQuaternion.setFromEuler( new THREE.Euler( 0, - Math.PI * 0.5, 0 ) );
+					break;
 
-			case 'negZ':
-				targetPosition.set( 0, 0, - 1 );
-				targetQuaternion.setFromEuler( new THREE.Euler( 0, Math.PI, 0 ) );
-				break;
+				case 'negY':
+					targetPosition.set( 0, - 1, 0 );
+					targetQuaternion.setFromEuler( new THREE.Euler( Math.PI * 0.5, 0, 0 ) );
+					break;
 
-			default:
-				console.error( 'ViewHelper: Invalid axis.' );
+				case 'negZ':
+					targetPosition.set( 0, 0, - 1 );
+					targetQuaternion.setFromEuler( new THREE.Euler( 0, Math.PI, 0 ) );
+					break;
 
-		}
+				default:
+					console.error( 'ViewHelper: Invalid axis.' );
 
-		//
+			}
 
-		radius = editorCamera.position.distanceTo( focusPoint );
-		targetPosition.multiplyScalar( radius ).add( focusPoint );
+			//
 
-		dummy.position.copy( focusPoint );
+			radius = editorCamera.position.distanceTo( focusPoint );
+			targetPosition.multiplyScalar( radius ).add( focusPoint );
 
-		dummy.lookAt( editorCamera.position );
-		q1.copy( dummy.quaternion );
+			dummy.position.copy( focusPoint );
 
-		dummy.lookAt( targetPosition );
-		q2.copy( dummy.quaternion );
+			dummy.lookAt( editorCamera.position );
+			q1.copy( dummy.quaternion );
 
-	}
+			dummy.lookAt( targetPosition );
+			q2.copy( dummy.quaternion );
 
-	function getAxisMaterial( color ) {
+		}
 
-		return new THREE.MeshBasicMaterial( { color: color, toneMapped: false } );
+		function getAxisMaterial( color ) {
 
-	}
+			return new THREE.MeshBasicMaterial( { color: color, toneMapped: false } );
 
-	function getSpriteMaterial( color, text = null ) {
-
-		var canvas = document.createElement( 'canvas' );
-		canvas.width = 64;
-		canvas.height = 64;
+		}
 
-		var context = canvas.getContext( '2d' );
-		context.beginPath();
-		context.arc( 32, 32, 16, 0, 2 * Math.PI );
-		context.closePath();
-		context.fillStyle = color.getStyle();
-		context.fill();
+		function getSpriteMaterial( color, text = null ) {
 
-		if ( text !== null ) {
+			const canvas = document.createElement( 'canvas' );
+			canvas.width = 64;
+			canvas.height = 64;
 
-			context.font = '24px Arial';
-			context.textAlign = 'center';
-			context.fillStyle = '#000000';
-			context.fillText( text, 32, 41 );
+			const context = canvas.getContext( '2d' );
+			context.beginPath();
+			context.arc( 32, 32, 16, 0, 2 * Math.PI );
+			context.closePath();
+			context.fillStyle = color.getStyle();
+			context.fill();
 
-		}
+			if ( text !== null ) {
 
-		var texture = new THREE.CanvasTexture( canvas );
+				context.font = '24px Arial';
+				context.textAlign = 'center';
+				context.fillStyle = '#000000';
+				context.fillText( text, 32, 41 );
 
-		return new THREE.SpriteMaterial( { map: texture, toneMapped: false } );
+			}
 
-	}
+			const texture = new THREE.CanvasTexture( canvas );
 
-}
+			return new THREE.SpriteMaterial( { map: texture, toneMapped: false } );
 
-ViewHelper.prototype = Object.assign( Object.create( THREE.Object3D.prototype ), {
+		}
 
-	constructor: ViewHelper,
+	}
 
-	isViewHelper: true
+}
 
-} );
+ViewHelper.prototype.isViewHelper = true;
 
 export { ViewHelper };

+ 1 - 1
editor/js/Viewport.js

@@ -347,7 +347,7 @@ function Viewport( editor ) {
 
 	} );
 
-	signals.rendererChanged.add( function ( newRenderer ) {
+	signals.rendererCreated.add( function ( newRenderer ) {
 
 		if ( renderer !== null ) {
 

+ 6 - 2
editor/sw.js

@@ -1,3 +1,5 @@
+// r128
+
 const cacheName = 'threejs-editor';
 
 const assets = [
@@ -13,7 +15,7 @@ const assets = [
 	'../examples/jsm/controls/TransformControls.js',
 
 	'../examples/jsm/libs/chevrotain.module.min.js',
-	'../examples/jsm/libs/fflate.module.min.js',
+	'../examples/jsm/libs/fflate.module.js',
 
 	'../examples/js/libs/draco/draco_decoder.js',
 	'../examples/js/libs/draco/draco_decoder.wasm',
@@ -57,6 +59,9 @@ const assets = [
 	'../examples/jsm/curves/NURBSCurve.js',
 	'../examples/jsm/curves/NURBSUtils.js',
 
+	'../examples/jsm/interactive/HTMLMesh.js',
+	'../examples/jsm/interactive/InteractiveGroup.js',
+
 	'../examples/jsm/environments/RoomEnvironment.js',
 
 	'../examples/jsm/exporters/ColladaExporter.js',
@@ -108,7 +113,6 @@ const assets = [
 	'./js/libs/tern-threejs/threejs.js',
 
 	'./js/libs/signals.min.js',
-	'./js/libs/three.html.js',
 	'./js/libs/ui.js',
 	'./js/libs/ui.three.js',
 

+ 2 - 1
examples/files.json

@@ -123,7 +123,6 @@
 		"webgl_loader_vrm",
 		"webgl_loader_vrml",
 		"webgl_loader_vtk",
-		"webgl_loader_x",
 		"webgl_loader_xyz",
 		"webgl_lod",
 		"webgl_marchingcubes",
@@ -233,6 +232,7 @@
 		"webgl_materials_envmaps_hdr_nodes",
 		"webgl_materials_envmaps_pmrem_nodes",
 		"webgl_materials_nodes",
+		"webgl_materials_standard_nodes",
 		"webgl_mirror_nodes",
 		"webgl_performance_nodes",
 		"webgl_postprocessing_nodes",
@@ -318,6 +318,7 @@
 	"webgpu": [
 		"webgpu_compute",
 		"webgpu_instance_uniform",
+		"webgpu_lights_selective",
 		"webgpu_materials",
 		"webgpu_rtt",
 		"webgpu_sandbox"

+ 1 - 1
examples/index.html

@@ -51,7 +51,7 @@
 
 		</div>
 
-		<iframe id="viewer" name="viewer" allowfullscreen allowvr onmousewheel=""></iframe>
+		<iframe id="viewer" name="viewer" allow="fullscreen; xr-spatial-tracking;"></iframe>
 
 		<a id="button" target="_blank"><img src="../files/ic_code_black_24dp.svg"></a>
 

+ 56 - 55
examples/js/WebGL.js

@@ -1,89 +1,90 @@
-THREE.WEBGL = {
+( function () {
 
-	isWebGLAvailable: function () {
+	class WEBGL {
 
-		try {
+		static isWebGLAvailable() {
 
-			var canvas = document.createElement( 'canvas' );
-			return !! ( window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ) );
+			try {
 
-		} catch ( e ) {
+				const canvas = document.createElement( 'canvas' );
+				return !! ( window.WebGLRenderingContext && ( canvas.getContext( 'webgl' ) || canvas.getContext( 'experimental-webgl' ) ) );
 
-			return false;
+			} catch ( e ) {
 
-		}
+				return false;
 
-	},
+			}
 
-	isWebGL2Available: function () {
+		}
 
-		try {
+		static isWebGL2Available() {
 
-			var canvas = document.createElement( 'canvas' );
-			return !! ( window.WebGL2RenderingContext && canvas.getContext( 'webgl2' ) );
+			try {
 
-		} catch ( e ) {
+				const canvas = document.createElement( 'canvas' );
+				return !! ( window.WebGL2RenderingContext && canvas.getContext( 'webgl2' ) );
 
-			return false;
+			} catch ( e ) {
 
-		}
+				return false;
 
-	},
+			}
 
-	getWebGLErrorMessage: function () {
+		}
 
-		return this.getErrorMessage( 1 );
+		static getWebGLErrorMessage() {
 
-	},
+			return this.getErrorMessage( 1 );
 
-	getWebGL2ErrorMessage: function () {
+		}
 
-		return this.getErrorMessage( 2 );
+		static getWebGL2ErrorMessage() {
 
-	},
+			return this.getErrorMessage( 2 );
 
-	getErrorMessage: function ( version ) {
+		}
 
-		var names = {
-			1: 'WebGL',
-			2: 'WebGL 2'
-		};
+		static getErrorMessage( version ) {
 
-		var contexts = {
-			1: window.WebGLRenderingContext,
-			2: window.WebGL2RenderingContext
-		};
+			const names = {
+				1: 'WebGL',
+				2: 'WebGL 2'
+			};
+			const contexts = {
+				1: window.WebGLRenderingContext,
+				2: window.WebGL2RenderingContext
+			};
+			let message = 'Your $0 does not seem to support <a href="http://khronos.org/webgl/wiki/Getting_a_WebGL_Implementation" style="color:#000">$1</a>';
+			const 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;
+
+} )();

+ 62 - 71
examples/js/animation/AnimationClipCreator.js

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

+ 231 - 265
examples/js/animation/CCDIKSolver.js

@@ -1,4 +1,27 @@
-/**
+( function () {
+
+	const _q = new THREE.Quaternion();
+
+	const _targetPos = new THREE.Vector3();
+
+	const _targetVec = new THREE.Vector3();
+
+	const _effectorPos = new THREE.Vector3();
+
+	const _effectorVec = new THREE.Vector3();
+
+	const _linkPos = new THREE.Vector3();
+
+	const _invLinkQ = new THREE.Quaternion();
+
+	const _linkScale = new THREE.Vector3();
+
+	const _axis = new THREE.Vector3();
+
+	const _vector = new THREE.Vector3();
+
+	const _matrix = new THREE.Matrix4();
+	/**
  * CCD Algorithm
  *  - https://sites.google.com/site/auraliusproject/ccd-algorithm
  *
@@ -18,35 +41,33 @@
  * } ];
  */
 
-THREE.CCDIKSolver = ( function () {
 
-	/**
-	 * @param {THREE.SkinnedMesh} mesh
-	 * @param {Array<Object>} iks
-	 */
-	function CCDIKSolver( mesh, iks ) {
+	class CCDIKSolver {
 
-		this.mesh = mesh;
-		this.iks = iks || [];
+		/**
+   * @param {THREE.SkinnedMesh} mesh
+   * @param {Array<Object>} iks
+   */
+		constructor( mesh, iks = [] ) {
 
-		this._valid();
+			this.mesh = mesh;
+			this.iks = iks;
 
-	}
+			this._valid();
 
-	CCDIKSolver.prototype = {
+		}
+		/**
+   * Update all IK bones.
+   *
+   * @return {CCDIKSolver}
+   */
 
-		constructor: CCDIKSolver,
 
-		/**
-		 * Update all IK bones.
-		 *
-		 * @return {CCDIKSolver}
-		 */
-		update: function () {
+		update() {
 
-			var iks = this.iks;
+			const iks = this.iks;
 
-			for ( var i = 0, il = iks.length; i < il; i ++ ) {
+			for ( let i = 0, il = iks.length; i < il; i ++ ) {
 
 				this.updateOne( iks[ i ] );
 
@@ -54,188 +75,161 @@ THREE.CCDIKSolver = ( function () {
 
 			return this;
 
-		},
-
+		}
 		/**
-		 * Update one IK bone
-		 *
-		 * @param {Object} ik parameter
-		 * @return {THREE.CCDIKSolver}
-		 */
-		updateOne: function () {
+   * Update one IK bone
+   *
+   * @param {Object} ik parameter
+   * @return {CCDIKSolver}
+   */
 
-			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 ) {
+		updateOne( ik ) {
 
-				var bones = this.mesh.skeleton.bones;
+			const bones = this.mesh.skeleton.bones; // for reference overhead reduction in loop
 
-				// for reference overhead reduction in loop
-				var math = Math;
+			const math = Math;
+			const effector = bones[ ik.effector ];
+			const target = bones[ ik.target ]; // don't use getWorldPosition() here for the performance
+			// because it calls updateMatrixWorld( true ) inside.
 
-				var effector = bones[ ik.effector ];
-				var target = bones[ ik.target ];
+			_targetPos.setFromMatrixPosition( target.matrixWorld );
 
-				// don't use getWorldPosition() here for the performance
-				// because it calls updateMatrixWorld( true ) inside.
-				targetPos.setFromMatrixPosition( target.matrixWorld );
+			const links = ik.links;
+			const iteration = ik.iteration !== undefined ? ik.iteration : 1;
 
-				var links = ik.links;
-				var iteration = ik.iteration !== undefined ? ik.iteration : 1;
+			for ( let i = 0; i < iteration; i ++ ) {
 
-				for ( var i = 0; i < iteration; i ++ ) {
+				let rotated = false;
 
-					var rotated = false;
+				for ( let j = 0, jl = links.length; j < jl; j ++ ) {
 
-					for ( var j = 0, jl = links.length; j < jl; j ++ ) {
+					const link = bones[ links[ j ].index ]; // skip this link and following links.
+					// this skip is used for MMD performance optimization.
 
-						var link = bones[ links[ j ].index ];
+					if ( links[ j ].enabled === false ) break;
+					const limitation = links[ j ].limitation;
+					const rotationMin = links[ j ].rotationMin;
+					const rotationMax = links[ j ].rotationMax; // don't use getWorldPosition/Quaternion() here for the performance
+					// because they call updateMatrixWorld( true ) inside.
 
-						// skip this link and following links.
-						// this skip is used for MMD performance optimization.
-						if ( links[ j ].enabled === false ) break;
+					link.matrixWorld.decompose( _linkPos, _invLinkQ, _linkScale );
 
-						var limitation = links[ j ].limitation;
-						var rotationMin = links[ j ].rotationMin;
-						var rotationMax = links[ j ].rotationMax;
+					_invLinkQ.invert();
 
-						// 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 );
+					_effectorPos.setFromMatrixPosition( effector.matrixWorld ); // work in link world
 
-						// work in link world
-						effectorVec.subVectors( effectorPos, linkPos );
-						effectorVec.applyQuaternion( invLinkQ );
-						effectorVec.normalize();
 
-						targetVec.subVectors( targetPos, linkPos );
-						targetVec.applyQuaternion( invLinkQ );
-						targetVec.normalize();
+					_effectorVec.subVectors( _effectorPos, _linkPos );
 
-						var angle = targetVec.dot( effectorVec );
+					_effectorVec.applyQuaternion( _invLinkQ );
 
-						if ( angle > 1.0 ) {
+					_effectorVec.normalize();
 
-							angle = 1.0;
+					_targetVec.subVectors( _targetPos, _linkPos );
 
-						} else if ( angle < - 1.0 ) {
+					_targetVec.applyQuaternion( _invLinkQ );
 
-							angle = - 1.0;
+					_targetVec.normalize();
 
-						}
+					let angle = _targetVec.dot( _effectorVec );
 
-						angle = math.acos( angle );
+					if ( angle > 1.0 ) {
 
-						// 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;
+						angle = 1.0;
 
-						if ( ik.minAngle !== undefined && angle < ik.minAngle ) {
+					} else if ( angle < - 1.0 ) {
 
-							angle = ik.minAngle;
+						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
 
-						if ( ik.maxAngle !== undefined && angle > ik.maxAngle ) {
+					if ( angle < 1e-5 ) continue;
 
-							angle = ik.maxAngle;
+					if ( ik.minAngle !== undefined && angle < ik.minAngle ) {
 
-						}
+						angle = ik.minAngle;
+
+					}
 
-						axis.crossVectors( effectorVec, targetVec );
-						axis.normalize();
+					if ( ik.maxAngle !== undefined && angle > ik.maxAngle ) {
 
-						q.setFromAxisAngle( axis, angle );
-						link.quaternion.multiply( q );
+						angle = ik.maxAngle;
 
-						// TODO: re-consider the limitation specification
-						if ( limitation !== undefined ) {
+					}
 
-							var c = link.quaternion.w;
+					_axis.crossVectors( _effectorVec, _targetVec );
 
-							if ( c > 1.0 ) c = 1.0;
+					_axis.normalize();
 
-							var c2 = math.sqrt( 1 - c * c );
-							link.quaternion.set( limitation.x * c2,
-							                     limitation.y * c2,
-							                     limitation.z * c2,
-							                     c );
+					_q.setFromAxisAngle( _axis, angle );
 
-						}
+					link.quaternion.multiply( _q ); // TODO: re-consider the limitation specification
 
-						if ( rotationMin !== undefined ) {
+					if ( limitation !== undefined ) {
 
-							link.rotation.setFromVector3(
-								link.rotation
-									.toVector3( vector )
-									.max( rotationMin ) );
+						let c = link.quaternion.w;
+						if ( c > 1.0 ) c = 1.0;
+						const c2 = math.sqrt( 1 - c * c );
+						link.quaternion.set( limitation.x * c2, limitation.y * c2, limitation.z * c2, c );
 
-						}
+					}
 
-						if ( rotationMax !== undefined ) {
+					if ( rotationMin !== undefined ) {
 
-							link.rotation.setFromVector3(
-								link.rotation
-									.toVector3( vector )
-									.min( rotationMax ) );
+						link.rotation.setFromVector3( link.rotation.toVector3( _vector ).max( rotationMin ) );
 
-						}
+					}
 
-						link.updateMatrixWorld( true );
+					if ( rotationMax !== undefined ) {
 
-						rotated = true;
+						link.rotation.setFromVector3( link.rotation.toVector3( _vector ).min( rotationMax ) );
 
 					}
 
-					if ( ! rotated ) break;
+					link.updateMatrixWorld( true );
+					rotated = true;
 
 				}
 
-				return this;
+				if ( ! rotated ) break;
 
-			};
+			}
 
-		}(),
+			return this;
 
+		}
 		/**
-		 * Creates Helper
-		 *
-		 * @return {CCDIKHelper}
-		 */
-		createHelper: function () {
+   * Creates Helper
+   *
+   * @return {CCDIKHelper}
+   */
 
-			return new CCDIKHelper( this.mesh, this.mesh.geometry.userData.MMD.iks );
 
-		},
+		createHelper() {
 
-		// private methods
+			return new CCDIKHelper( this.mesh, this.mesh.geometry.userData.MMD.iks );
 
-		_valid: function () {
+		} // private methods
 
-			var iks = this.iks;
-			var bones = this.mesh.skeleton.bones;
 
-			for ( var i = 0, il = iks.length; i < il; i ++ ) {
+		_valid() {
 
-				var ik = iks[ i ];
-				var effector = bones[ ik.effector ];
-				var links = ik.links;
-				var link0, link1;
+			const iks = this.iks;
+			const bones = this.mesh.skeleton.bones;
 
+			for ( let i = 0, il = iks.length; i < il; i ++ ) {
+
+				const ik = iks[ i ];
+				const effector = bones[ ik.effector ];
+				const links = ik.links;
+				let link0, link1;
 				link0 = effector;
 
-				for ( var j = 0, jl = links.length; j < jl; j ++ ) {
+				for ( let j = 0, jl = links.length; j < jl; j ++ ) {
 
 					link1 = bones[ links[ j ].index ];
 
@@ -253,166 +247,139 @@ THREE.CCDIKSolver = ( function () {
 
 		}
 
-	};
-
-	/**
-	 * 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 );
-
-		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,
-
-		/**
-		 * Updates IK bones visualization.
-		 */
-		updateMatrixWorld: function () {
-
-			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 );
-
-			}
-
-			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 _vector.setFromMatrixPosition( bone.matrixWorld ).applyMatrix4( matrixWorldInv );
 
-			return function updateMatrixWorld( force ) {
-
-				var mesh = this.root;
+	}
 
-				if ( this.visible ) {
+	function setPositionOfBoneToAttributeArray( array, index, bone, matrixWorldInv ) {
 
-					var offset = 0;
+		const v = getPosition( bone, matrixWorldInv );
+		array[ index * 3 + 0 ] = v.x;
+		array[ index * 3 + 1 ] = v.y;
+		array[ index * 3 + 2 ] = v.z;
 
-					var iks = this.iks;
-					var bones = mesh.skeleton.bones;
+	}
+	/**
+ * Visualize IK bones
+ *
+ * @param {SkinnedMesh} mesh
+ * @param {Array<Object>} iks
+ */
 
-					matrix.copy( mesh.matrixWorld ).invert();
 
-					for ( var i = 0, il = iks.length; i < il; i ++ ) {
+	class CCDIKHelper extends THREE.Object3D {
+
+		constructor( mesh, iks = [] ) {
+
+			super();
+			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();
 
-						var ik = iks[ i ];
+		}
+		/**
+   * Updates IK bones visualization.
+   */
 
-						var targetBone = bones[ ik.target ];
-						var effectorBone = bones[ ik.effector ];
 
-						var targetMesh = this.children[ offset ++ ];
-						var effectorMesh = this.children[ offset ++ ];
+		updateMatrixWorld( force ) {
 
-						targetMesh.position.copy( getPosition( targetBone, matrix ) );
-						effectorMesh.position.copy( getPosition( effectorBone, matrix ) );
+			const mesh = this.root;
 
-						for ( var j = 0, jl = ik.links.length; j < jl; j ++ ) {
+			if ( this.visible ) {
 
-							var link = ik.links[ j ];
-							var linkBone = bones[ link.index ];
+				let offset = 0;
+				const iks = this.iks;
+				const bones = mesh.skeleton.bones;
 
-							var linkMesh = this.children[ offset ++ ];
+				_matrix.copy( mesh.matrixWorld ).invert();
 
-							linkMesh.position.copy( getPosition( linkBone, matrix ) );
+				for ( let i = 0, il = iks.length; i < il; i ++ ) {
 
-						}
+					const ik = iks[ i ];
+					const targetBone = bones[ ik.target ];
+					const effectorBone = bones[ ik.effector ];
+					const targetMesh = this.children[ offset ++ ];
+					const 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;
+					for ( let j = 0, jl = ik.links.length; j < jl; j ++ ) {
 
-						setPositionOfBoneToAttributeArray( array, 0, targetBone, matrix );
-						setPositionOfBoneToAttributeArray( array, 1, effectorBone, matrix );
+						const link = ik.links[ j ];
+						const linkBone = bones[ link.index ];
+						const linkMesh = this.children[ offset ++ ];
+						linkMesh.position.copy( getPosition( linkBone, _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 );
+					const line = this.children[ offset ++ ];
+					const array = line.geometry.attributes.position.array;
+					setPositionOfBoneToAttributeArray( array, 0, targetBone, _matrix );
+					setPositionOfBoneToAttributeArray( array, 1, effectorBone, _matrix );
 
-						}
+					for ( let j = 0, jl = ik.links.length; j < jl; j ++ ) {
 
-						line.geometry.attributes.position.needsUpdate = true;
+						const link = ik.links[ j ];
+						const 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 );
+			super.updateMatrixWorld( force );
 
-		}(),
+		} // private method
 
-		// private method
 
-		_init: function () {
+		_init() {
 
-			var scope = this;
-			var iks = this.iks;
+			const scope = this;
+			const iks = this.iks;
 
 			function createLineGeometry( ik ) {
 
-				var geometry = new THREE.BufferGeometry();
-				var vertices = new Float32Array( ( 2 + ik.links.length ) * 3 );
+				const geometry = new THREE.BufferGeometry();
+				const vertices = new Float32Array( ( 2 + ik.links.length ) * 3 );
 				geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
-
 				return geometry;
 
 			}
@@ -441,14 +408,13 @@ THREE.CCDIKSolver = ( function () {
 
 			}
 
-			for ( var i = 0, il = iks.length; i < il; i ++ ) {
-
-				var ik = iks[ i ];
+			for ( let i = 0, il = iks.length; i < il; i ++ ) {
 
+				const ik = iks[ i ];
 				this.add( createTargetMesh() );
 				this.add( createEffectorMesh() );
 
-				for ( var j = 0, jl = ik.links.length; j < jl; j ++ ) {
+				for ( let j = 0, jl = ik.links.length; j < jl; j ++ ) {
 
 					this.add( createLinkMesh() );
 
@@ -460,8 +426,8 @@ THREE.CCDIKSolver = ( function () {
 
 		}
 
-	} );
+	}
 
-	return CCDIKSolver;
+	THREE.CCDIKSolver = CCDIKSolver;
 
 } )();

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 231 - 398
examples/js/animation/MMDAnimationHelper.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 268 - 340
examples/js/animation/MMDPhysics.js


+ 114 - 152
examples/js/cameras/CinematicCamera.js

@@ -1,200 +1,162 @@
-THREE.CinematicCamera = function ( fov, aspect, near, far ) {
+( function () {
 
-	THREE.PerspectiveCamera.call( this, fov, aspect, near, far );
+	class CinematicCamera extends THREE.PerspectiveCamera {
 
-	this.type = 'CinematicCamera';
+		constructor( fov, aspect, near, far ) {
 
-	this.postprocessing = { enabled: true };
-	this.shaderSettings = {
-		rings: 3,
-		samples: 4
-	};
+			super( fov, aspect, near, far );
+			this.type = 'CinematicCamera';
+			this.postprocessing = {
+				enabled: true
+			};
+			this.shaderSettings = {
+				rings: 3,
+				samples: 4
+			};
+			const 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
 
-	var depthShader = THREE.BokehDepthShader;
+			this.setLens();
+			this.initPostProcessing();
 
-	this.materialDepth = new THREE.ShaderMaterial( {
-		uniforms: depthShader.uniforms,
-		vertexShader: depthShader.vertexShader,
-		fragmentShader: depthShader.fragmentShader
-	} );
+		} // providing fnumber and coc(Circle of Confusion) as extra arguments
+		// In case of cinematicCamera, having a default lens set is important
+		// if fnumber and coc are not provided, cinematicCamera tries to act as a basic THREE.PerspectiveCamera
 
-	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();
+		setLens( focalLength = 35, filmGauge = 35, fNumber = 8, coc = 0.019 ) {
 
-	this.initPostProcessing();
+			this.filmGauge = filmGauge;
+			this.setFocalLength( focalLength );
+			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
 
-THREE.CinematicCamera.prototype = Object.create( THREE.PerspectiveCamera.prototype );
-THREE.CinematicCamera.prototype.constructor = THREE.CinematicCamera;
+			this.hyperFocal = focalLength * focalLength / ( this.aperture * this.coc );
 
+		}
 
-// providing fnumber and coc(Circle of Confusion) as extra arguments
-THREE.CinematicCamera.prototype.setLens = function ( focalLength, filmGauge, fNumber, coc ) {
+		linearize( depth ) {
 
-	// In case of cinematicCamera, having a default lens set is important
-	if ( focalLength === undefined ) focalLength = 35;
-	if ( filmGauge !== undefined ) this.filmGauge = filmGauge;
+			const zfar = this.far;
+			const znear = this.near;
+			return - zfar * znear / ( depth * ( zfar - znear ) - zfar );
 
-	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;
+		smoothstep( near, far, depth ) {
 
-	this.fNumber = fNumber;
-	this.coc = coc;
+			const x = this.saturate( ( depth - near ) / ( far - near ) );
+			return x * x * ( 3 - 2 * x );
 
-	// 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 );
+		saturate( x ) {
 
-};
+			return Math.max( 0, Math.min( 1, x ) );
 
-THREE.CinematicCamera.prototype.linearize = function ( depth ) {
+		} // function for focusing at a distance from the camera
 
-	var zfar = this.far;
-	var znear = this.near;
-	return - zfar * znear / ( depth * ( zfar - znear ) - zfar );
 
-};
+		focusAt( focusDistance = 20 ) {
 
-THREE.CinematicCamera.prototype.smoothstep = function ( near, far, depth ) {
+			const focalLength = this.getFocalLength(); // distance from the camera (normal to frustrum) to focus on
 
-	var x = this.saturate( ( depth - near ) / ( far - near ) );
-	return x * x * ( 3 - 2 * x );
+			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)
 
-THREE.CinematicCamera.prototype.saturate = function ( x ) {
+			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)
 
-	return Math.max( 0, Math.min( 1, x ) );
+			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;
 
-// function for focusing at a distance from the camera
-THREE.CinematicCamera.prototype.focusAt = function ( focusDistance ) {
+		}
 
-	if ( focusDistance === undefined ) focusDistance = 20;
+		initPostProcessing() {
 
-	var focalLength = this.getFocalLength();
+			if ( this.postprocessing.enabled ) {
 
-	// distance from the camera (normal to frustrum) to focus on
-	this.focus = focusDistance;
+				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 );
+				const 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 );
+				const 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 );
 
-	// the nearest point from the camera which is in focus (unused)
-	this.nearPoint = ( this.hyperFocal * this.focus ) / ( this.hyperFocal + ( this.focus - focalLength ) );
+				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 );
 
-	// 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 );
-
-		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;
+		renderCinematic( scene, renderer ) {
 
-		//console.log( this.postprocessing.bokeh_uniforms[ "focalDepth" ].value );
+			if ( this.postprocessing.enabled ) {
 
-		this.postprocessing.bokeh_uniforms[ 'znear' ].value = this.near;
-		this.postprocessing.bokeh_uniforms[ 'zfar' ].value = this.near;
+				const currentRenderTarget = renderer.getRenderTarget();
+				renderer.clear(); // Render scene into texture
 
+				scene.overrideMaterial = null;
+				renderer.setRenderTarget( this.postprocessing.rtTextureColor );
+				renderer.clear();
+				renderer.render( scene, this ); // Render depth into texture
 
-		this.postprocessing.bokeh_uniforms[ 'textureWidth' ].value = window.innerWidth;
+				scene.overrideMaterial = this.materialDepth;
+				renderer.setRenderTarget( this.postprocessing.rtTextureDepth );
+				renderer.clear();
+				renderer.render( scene, this ); // Render bokeh composite
 
-		this.postprocessing.bokeh_uniforms[ 'textureHeight' ].value = window.innerHeight;
+				renderer.setRenderTarget( null );
+				renderer.render( this.postprocessing.scene, this.postprocessing.camera );
+				renderer.setRenderTarget( currentRenderTarget );
 
-		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 );
+		}
 
 	}
 
-};
-
-THREE.CinematicCamera.prototype.renderCinematic = function ( scene, renderer ) {
-
-	if ( this.postprocessing.enabled ) {
-
-		var currentRenderTarget = renderer.getRenderTarget();
-
-		renderer.clear();
-
-		// Render scene 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 );
-
-		// Render bokeh composite
-
-		renderer.setRenderTarget( null );
-		renderer.render( this.postprocessing.scene, this.postprocessing.camera );
-
-		renderer.setRenderTarget( currentRenderTarget );
-
-	}
+	THREE.CinematicCamera = CinematicCamera;
 
-};
+} )();

+ 80 - 88
examples/js/controls/DeviceOrientationControls.js

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

+ 220 - 179
examples/js/controls/DragControls.js

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

+ 219 - 217
examples/js/controls/FirstPersonControls.js

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

+ 247 - 188
examples/js/controls/FlyControls.js

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

+ 654 - 824
examples/js/controls/OrbitControls.js

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

+ 94 - 90
examples/js/controls/PointerLockControls.js

@@ -1,156 +1,160 @@
-THREE.PointerLockControls = function ( camera, domElement ) {
+( function () {
 
-	if ( domElement === undefined ) {
+	const _euler = new THREE.Euler( 0, 0, 0, 'YXZ' );
 
-		console.warn( 'THREE.PointerLockControls: The second parameter "domElement" is now mandatory.' );
-		domElement = document.body;
+	const _vector = new THREE.Vector3();
 
-	}
+	const _changeEvent = {
+		type: 'change'
+	};
+	const _lockEvent = {
+		type: 'lock'
+	};
+	const _unlockEvent = {
+		type: 'unlock'
+	};
 
-	this.domElement = domElement;
-	this.isLocked = false;
+	const _PI_2 = Math.PI / 2;
 
-	// Set to constrain the pitch of the camera
-	// Range is 0 to Math.PI radians
-	this.minPolarAngle = 0; // radians
-	this.maxPolarAngle = Math.PI; // radians
+	class PointerLockControls extends THREE.EventDispatcher {
 
-	//
-	// internals
-	//
+		constructor( camera, domElement ) {
 
-	var scope = this;
+			super();
 
-	var changeEvent = { type: 'change' };
-	var lockEvent = { type: 'lock' };
-	var unlockEvent = { type: 'unlock' };
+			if ( domElement === undefined ) {
 
-	var euler = new THREE.Euler( 0, 0, 0, 'YXZ' );
+				console.warn( 'THREE.PointerLockControls: The second parameter "domElement" is now mandatory.' );
+				domElement = document.body;
 
-	var PI_2 = Math.PI / 2;
+			}
 
-	var vec = new THREE.Vector3();
+			this.domElement = domElement;
+			this.isLocked = false; // Set to constrain the pitch of the camera
+			// Range is 0 to Math.PI radians
 
-	function onMouseMove( event ) {
+			this.minPolarAngle = 0; // radians
 
-		if ( scope.isLocked === false ) return;
+			this.maxPolarAngle = Math.PI; // radians
 
-		var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
-		var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
+			const scope = this;
 
-		euler.setFromQuaternion( camera.quaternion );
+			function onMouseMove( event ) {
 
-		euler.y -= movementX * 0.002;
-		euler.x -= movementY * 0.002;
+				if ( scope.isLocked === false ) return;
+				const movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
+				const movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
 
-		euler.x = Math.max( PI_2 - scope.maxPolarAngle, Math.min( PI_2 - scope.minPolarAngle, euler.x ) );
+				_euler.setFromQuaternion( camera.quaternion );
 
-		camera.quaternion.setFromEuler( euler );
+				_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 );
 
-		scope.dispatchEvent( changeEvent );
+			}
 
-	}
+			function onPointerlockChange() {
 
-	function onPointerlockChange() {
+				if ( scope.domElement.ownerDocument.pointerLockElement === scope.domElement ) {
 
-		if ( scope.domElement.ownerDocument.pointerLockElement === scope.domElement ) {
+					scope.dispatchEvent( _lockEvent );
+					scope.isLocked = true;
 
-			scope.dispatchEvent( lockEvent );
+				} else {
 
-			scope.isLocked = true;
+					scope.dispatchEvent( _unlockEvent );
+					scope.isLocked = false;
 
-		} else {
+				}
 
-			scope.dispatchEvent( unlockEvent );
+			}
 
-			scope.isLocked = false;
+			function onPointerlockError() {
 
-		}
+				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;
+				const direction = new THREE.Vector3( 0, 0, - 1 );
+				return function ( v ) {
 
-	};
-
-	this.getDirection = function () {
-
-		var direction = new THREE.Vector3( 0, 0, - 1 );
+					return v.copy( direction ).applyQuaternion( camera.quaternion );
 
-		return function ( v ) {
+				};
 
-			return v.copy( direction ).applyQuaternion( camera.quaternion );
+			}();
 
-		};
+			this.moveForward = function ( distance ) {
 
-	}();
+				// move forward parallel to the xz-plane
+				// assumes camera.up is y-up
+				_vector.setFromMatrixColumn( camera.matrix, 0 );
 
-	this.moveForward = function ( distance ) {
+				_vector.crossVectors( camera.up, _vector );
 
-		// move forward parallel to the xz-plane
-		// assumes camera.up is y-up
+				camera.position.addScaledVector( _vector, distance );
 
-		vec.setFromMatrixColumn( camera.matrix, 0 );
+			};
 
-		vec.crossVectors( camera.up, vec );
+			this.moveRight = function ( distance ) {
 
-		camera.position.addScaledVector( vec, distance );
-
-	};
+				_vector.setFromMatrixColumn( camera.matrix, 0 );
 
-	this.moveRight = function ( distance ) {
+				camera.position.addScaledVector( _vector, distance );
 
-		vec.setFromMatrixColumn( camera.matrix, 0 );
+			};
 
-		camera.position.addScaledVector( vec, distance );
+			this.lock = function () {
 
-	};
+				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();
+	}
 
-};
+	THREE.PointerLockControls = PointerLockControls;
 
-THREE.PointerLockControls.prototype = Object.create( THREE.EventDispatcher.prototype );
-THREE.PointerLockControls.prototype.constructor = THREE.PointerLockControls;
+} )();

+ 466 - 482
examples/js/controls/TrackballControls.js

@@ -1,739 +1,723 @@
-THREE.TrackballControls = function ( object, domElement ) {
+( function () {
 
-	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*/ ];
+	const _changeEvent = {
+		type: 'change'
+	};
+	const _startEvent = {
+		type: 'start'
+	};
+	const _endEvent = {
+		type: 'end'
+	};
 
-	this.mouseButtons = { LEFT: THREE.MOUSE.ROTATE, MIDDLE: THREE.MOUSE.DOLLY, RIGHT: THREE.MOUSE.PAN };
+	class TrackballControls extends THREE.EventDispatcher {
+
+		constructor( object, domElement ) {
+
+			super();
+			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.' );
+			const scope = this;
+			const 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();
+			const EPS = 0.000001;
+			const lastPosition = new THREE.Vector3();
+			let lastZoom = 1;
+			let _state = STATE.NONE,
+				_keyState = STATE.NONE,
+				_touchZoomDistanceStart = 0,
+				_touchZoomDistanceEnd = 0,
+				_lastAngle = 0;
+
+			const _eye = new THREE.Vector3(),
+				_movePrev = new THREE.Vector2(),
+				_moveCurr = new THREE.Vector2(),
+				_lastAxis = new THREE.Vector3(),
+				_zoomStart = new THREE.Vector2(),
+				_zoomEnd = new THREE.Vector2(),
+				_panStart = new THREE.Vector2(),
+				_panEnd = new THREE.Vector2(); // for reset
 
-	// internals
 
-	this.target = new THREE.Vector3();
+			this.target0 = this.target.clone();
+			this.position0 = this.object.position.clone();
+			this.up0 = this.object.up.clone();
+			this.zoom0 = this.object.zoom; // methods
 
-	var EPS = 0.000001;
+			this.handleResize = function () {
 
-	var lastPosition = new THREE.Vector3();
-	var lastZoom = 1;
+				const box = scope.domElement.getBoundingClientRect(); // adjustments come from similar code in the jquery offset() function
 
-	var _state = STATE.NONE,
-		_keyState = STATE.NONE,
+				const 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;
 
-		_eye = new THREE.Vector3(),
+			};
 
-		_movePrev = new THREE.Vector2(),
-		_moveCurr = new THREE.Vector2(),
+			const getMouseOnScreen = function () {
 
-		_lastAxis = new THREE.Vector3(),
-		_lastAngle = 0,
+				const vector = new THREE.Vector2();
+				return function getMouseOnScreen( pageX, pageY ) {
 
-		_zoomStart = new THREE.Vector2(),
-		_zoomEnd = new THREE.Vector2(),
+					vector.set( ( pageX - scope.screen.left ) / scope.screen.width, ( pageY - scope.screen.top ) / scope.screen.height );
+					return vector;
 
-		_touchZoomDistanceStart = 0,
-		_touchZoomDistanceEnd = 0,
+				};
 
-		_panStart = new THREE.Vector2(),
-		_panEnd = new THREE.Vector2();
+			}();
 
-	// for reset
+			const getMouseOnCircle = function () {
 
-	this.target0 = this.target.clone();
-	this.position0 = this.object.position.clone();
-	this.up0 = this.object.up.clone();
-	this.zoom0 = this.object.zoom;
+				const vector = new THREE.Vector2();
+				return function getMouseOnCircle( pageX, pageY ) {
 
-	// events
+					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;
 
-	var changeEvent = { type: 'change' };
-	var startEvent = { type: 'start' };
-	var endEvent = { type: 'end' };
+				};
 
+			}();
 
-	// methods
+			this.rotateCamera = function () {
 
-	this.handleResize = function () {
+				const axis = new THREE.Vector3(),
+					quaternion = new THREE.Quaternion(),
+					eyeDirection = new THREE.Vector3(),
+					objectUpDirection = new THREE.Vector3(),
+					objectSidewaysDirection = new THREE.Vector3(),
+					moveDirection = new THREE.Vector3();
+				return function rotateCamera() {
 
-		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;
+					moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 );
+					let angle = moveDirection.length();
 
-	};
+					if ( angle ) {
 
-	var getMouseOnScreen = ( function () {
+						_eye.copy( scope.object.position ).sub( scope.target );
 
-		var vector = new THREE.Vector2();
+						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 );
 
-		return function getMouseOnScreen( pageX, pageY ) {
+						_eye.applyQuaternion( quaternion );
 
-			vector.set(
-				( pageX - scope.screen.left ) / scope.screen.width,
-				( pageY - scope.screen.top ) / scope.screen.height
-			);
+						scope.object.up.applyQuaternion( quaternion );
 
-			return vector;
+						_lastAxis.copy( axis );
 
-		};
+						_lastAngle = angle;
 
-	}() );
+					} else if ( ! scope.staticMoving && _lastAngle ) {
 
-	var getMouseOnCircle = ( function () {
+						_lastAngle *= Math.sqrt( 1.0 - scope.dynamicDampingFactor );
 
-		var vector = new THREE.Vector2();
+						_eye.copy( scope.object.position ).sub( scope.target );
 
-		return function getMouseOnCircle( pageX, pageY ) {
+						quaternion.setFromAxisAngle( _lastAxis, _lastAngle );
 
-			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
-			);
+						_eye.applyQuaternion( quaternion );
 
-			return vector;
+						scope.object.up.applyQuaternion( quaternion );
 
-		};
+					}
 
-	}() );
+					_movePrev.copy( _moveCurr );
 
-	this.rotateCamera = ( 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;
+			}();
 
-		return function rotateCamera() {
+			this.zoomCamera = function () {
 
-			moveDirection.set( _moveCurr.x - _movePrev.x, _moveCurr.y - _movePrev.y, 0 );
-			angle = moveDirection.length();
+				let factor;
 
-			if ( angle ) {
+				if ( _state === STATE.TOUCH_ZOOM_PAN ) {
 
-				_eye.copy( scope.object.position ).sub( scope.target );
+					factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
+					_touchZoomDistanceStart = _touchZoomDistanceEnd;
 
-				eyeDirection.copy( _eye ).normalize();
-				objectUpDirection.copy( scope.object.up ).normalize();
-				objectSidewaysDirection.crossVectors( objectUpDirection, eyeDirection ).normalize();
+					if ( scope.object.isPerspectiveCamera ) {
 
-				objectUpDirection.setLength( _moveCurr.y - _movePrev.y );
-				objectSidewaysDirection.setLength( _moveCurr.x - _movePrev.x );
+						_eye.multiplyScalar( factor );
 
-				moveDirection.copy( objectUpDirection.add( objectSidewaysDirection ) );
+					} else if ( scope.object.isOrthographicCamera ) {
 
-				axis.crossVectors( moveDirection, _eye ).normalize();
+						scope.object.zoom *= factor;
+						scope.object.updateProjectionMatrix();
 
-				angle *= scope.rotateSpeed;
-				quaternion.setFromAxisAngle( axis, angle );
+					} else {
 
-				_eye.applyQuaternion( quaternion );
-				scope.object.up.applyQuaternion( quaternion );
+						console.warn( 'THREE.TrackballControls: Unsupported camera type' );
 
-				_lastAxis.copy( axis );
-				_lastAngle = angle;
+					}
 
-			} else if ( ! scope.staticMoving && _lastAngle ) {
+				} else {
 
-				_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 );
+					factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * scope.zoomSpeed;
 
-			}
+					if ( factor !== 1.0 && factor > 0.0 ) {
 
-			_movePrev.copy( _moveCurr );
+						if ( scope.object.isPerspectiveCamera ) {
 
-		};
+							_eye.multiplyScalar( factor );
 
-	}() );
+						} else if ( scope.object.isOrthographicCamera ) {
 
+							scope.object.zoom /= factor;
+							scope.object.updateProjectionMatrix();
 
-	this.zoomCamera = function () {
+						} else {
 
-		var factor;
+							console.warn( 'THREE.TrackballControls: Unsupported camera type' );
 
-		if ( _state === STATE.TOUCH_ZOOM_PAN ) {
+						}
 
-			factor = _touchZoomDistanceStart / _touchZoomDistanceEnd;
-			_touchZoomDistanceStart = _touchZoomDistanceEnd;
+					}
 
-			if ( scope.object.isPerspectiveCamera ) {
+					if ( scope.staticMoving ) {
 
-				_eye.multiplyScalar( factor );
+						_zoomStart.copy( _zoomEnd );
 
-			} else if ( scope.object.isOrthographicCamera ) {
+					} else {
 
-				scope.object.zoom *= factor;
-				scope.object.updateProjectionMatrix();
+						_zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;
 
-			} else {
+					}
 
-				console.warn( 'THREE.TrackballControls: Unsupported camera type' );
+				}
 
-			}
+			};
 
-		} else {
+			this.panCamera = function () {
 
-			factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * scope.zoomSpeed;
+				const mouseChange = new THREE.Vector2(),
+					objectUp = new THREE.Vector3(),
+					pan = new THREE.Vector3();
+				return function panCamera() {
 
-			if ( factor !== 1.0 && factor > 0.0 ) {
+					mouseChange.copy( _panEnd ).sub( _panStart );
 
-				if ( scope.object.isPerspectiveCamera ) {
+					if ( mouseChange.lengthSq() ) {
 
-					_eye.multiplyScalar( factor );
+						if ( scope.object.isOrthographicCamera ) {
 
-				} else if ( scope.object.isOrthographicCamera ) {
+							const scale_x = ( scope.object.right - scope.object.left ) / scope.object.zoom / scope.domElement.clientWidth;
+							const scale_y = ( scope.object.top - scope.object.bottom ) / scope.object.zoom / scope.domElement.clientWidth;
+							mouseChange.x *= scale_x;
+							mouseChange.y *= scale_y;
 
-					scope.object.zoom /= factor;
-					scope.object.updateProjectionMatrix();
+						}
 
-				} else {
+						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 );
 
-					console.warn( 'THREE.TrackballControls: Unsupported camera type' );
+						if ( scope.staticMoving ) {
 
-				}
+							_panStart.copy( _panEnd );
 
-			}
+						} else {
 
-			if ( scope.staticMoving ) {
+							_panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( scope.dynamicDampingFactor ) );
 
-				_zoomStart.copy( _zoomEnd );
+						}
 
-			} else {
+					}
 
-				_zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;
+				};
 
-			}
+			}();
 
-		}
+			this.checkDistances = function () {
 
-	};
+				if ( ! scope.noZoom || ! scope.noPan ) {
 
-	this.panCamera = ( function () {
+					if ( _eye.lengthSq() > scope.maxDistance * scope.maxDistance ) {
 
-		var mouseChange = new THREE.Vector2(),
-			objectUp = new THREE.Vector3(),
-			pan = new THREE.Vector3();
+						scope.object.position.addVectors( scope.target, _eye.setLength( scope.maxDistance ) );
 
-		return function panCamera() {
+						_zoomStart.copy( _zoomEnd );
 
-			mouseChange.copy( _panEnd ).sub( _panStart );
+					}
 
-			if ( mouseChange.lengthSq() ) {
+					if ( _eye.lengthSq() < scope.minDistance * scope.minDistance ) {
 
-				if ( scope.object.isOrthographicCamera ) {
+						scope.object.position.addVectors( scope.target, _eye.setLength( scope.minDistance ) );
 
-					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;
+						_zoomStart.copy( _zoomEnd );
 
-					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 );
+			this.update = function () {
 
-				if ( scope.staticMoving ) {
+				_eye.subVectors( scope.object.position, scope.target );
 
-					_panStart.copy( _panEnd );
+				if ( ! scope.noRotate ) {
 
-				} else {
-
-					_panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( scope.dynamicDampingFactor ) );
+					scope.rotateCamera();
 
 				}
 
-			}
-
-		};
-
-	}() );
-
-	this.checkDistances = function () {
-
-		if ( ! scope.noZoom || ! scope.noPan ) {
+				if ( ! scope.noZoom ) {
 
-			if ( _eye.lengthSq() > scope.maxDistance * scope.maxDistance ) {
+					scope.zoomCamera();
 
-				scope.object.position.addVectors( scope.target, _eye.setLength( scope.maxDistance ) );
-				_zoomStart.copy( _zoomEnd );
-
-			}
-
-			if ( _eye.lengthSq() < scope.minDistance * scope.minDistance ) {
-
-				scope.object.position.addVectors( scope.target, _eye.setLength( scope.minDistance ) );
-				_zoomStart.copy( _zoomEnd );
-
-			}
-
-		}
-
-	};
-
-	this.update = function () {
-
-		_eye.subVectors( scope.object.position, scope.target );
+				}
 
-		if ( ! scope.noRotate ) {
+				if ( ! scope.noPan ) {
 
-			scope.rotateCamera();
+					scope.panCamera();
 
-		}
+				}
 
-		if ( ! scope.noZoom ) {
+				scope.object.position.addVectors( scope.target, _eye );
 
-			scope.zoomCamera();
+				if ( scope.object.isPerspectiveCamera ) {
 
-		}
+					scope.checkDistances();
+					scope.object.lookAt( scope.target );
 
-		if ( ! scope.noPan ) {
+					if ( lastPosition.distanceToSquared( scope.object.position ) > EPS ) {
 
-			scope.panCamera();
+						scope.dispatchEvent( _changeEvent );
+						lastPosition.copy( scope.object.position );
 
-		}
+					}
 
-		scope.object.position.addVectors( scope.target, _eye );
+				} else if ( scope.object.isOrthographicCamera ) {
 
-		if ( scope.object.isPerspectiveCamera ) {
+					scope.object.lookAt( scope.target );
 
-			scope.checkDistances();
+					if ( lastPosition.distanceToSquared( scope.object.position ) > EPS || lastZoom !== scope.object.zoom ) {
 
-			scope.object.lookAt( scope.target );
+						scope.dispatchEvent( _changeEvent );
+						lastPosition.copy( scope.object.position );
+						lastZoom = scope.object.zoom;
 
-			if ( lastPosition.distanceToSquared( scope.object.position ) > EPS ) {
+					}
 
-				scope.dispatchEvent( changeEvent );
+				} else {
 
-				lastPosition.copy( scope.object.position );
+					console.warn( 'THREE.TrackballControls: Unsupported camera type' );
 
-			}
+				}
 
-		} else if ( scope.object.isOrthographicCamera ) {
+			};
 
-			scope.object.lookAt( scope.target );
+			this.reset = function () {
 
-			if ( lastPosition.distanceToSquared( scope.object.position ) > EPS || lastZoom !== scope.object.zoom ) {
+				_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();
 
-				scope.dispatchEvent( changeEvent );
+				_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
 
-		} else {
 
-			console.warn( 'THREE.TrackballControls: Unsupported camera type' );
+			function onPointerDown( event ) {
 
-		}
-
-	};
-
-	this.reset = function () {
+				if ( scope.enabled === false ) return;
 
-		_state = STATE.NONE;
-		_keyState = STATE.NONE;
+				switch ( event.pointerType ) {
 
-		scope.target.copy( scope.target0 );
-		scope.object.position.copy( scope.position0 );
-		scope.object.up.copy( scope.up0 );
-		scope.object.zoom = scope.zoom0;
+					case 'mouse':
+					case 'pen':
+						onMouseDown( event );
+						break;
+        // TODO touch
 
-		scope.object.updateProjectionMatrix();
-
-		_eye.subVectors( scope.object.position, scope.target );
-
-		scope.object.lookAt( scope.target );
-
-		scope.dispatchEvent( changeEvent );
+				}
 
-		lastPosition.copy( scope.object.position );
-		lastZoom = scope.object.zoom;
+			}
 
-	};
+			function onPointerMove( event ) {
 
-	// listeners
+				if ( scope.enabled === false ) return;
 
-	function onPointerDown( event ) {
+				switch ( event.pointerType ) {
 
-		if ( scope.enabled === false ) return;
+					case 'mouse':
+					case 'pen':
+						onMouseMove( event );
+						break;
+        // TODO touch
 
-		switch ( event.pointerType ) {
+				}
 
-			case 'mouse':
-			case 'pen':
-				onMouseDown( event );
-				break;
+			}
 
-			// TODO touch
+			function onPointerUp( event ) {
 
-		}
+				if ( scope.enabled === false ) return;
 
-	}
+				switch ( event.pointerType ) {
 
-	function onPointerMove( event ) {
+					case 'mouse':
+					case 'pen':
+						onMouseUp( event );
+						break;
+        // TODO touch
 
-		if ( scope.enabled === false ) return;
+				}
 
-		switch ( event.pointerType ) {
+			}
 
-			case 'mouse':
-			case 'pen':
-				onMouseMove( event );
-				break;
+			function keydown( event ) {
 
-			// TODO touch
+				if ( scope.enabled === false ) return;
+				window.removeEventListener( 'keydown', keydown );
 
-		}
+				if ( _keyState !== STATE.NONE ) {
 
-	}
+					return;
 
-	function onPointerUp( event ) {
+				} else if ( event.code === scope.keys[ STATE.ROTATE ] && ! scope.noRotate ) {
 
-		if ( scope.enabled === false ) return;
+					_keyState = STATE.ROTATE;
 
-		switch ( event.pointerType ) {
+				} else if ( event.code === scope.keys[ STATE.ZOOM ] && ! scope.noZoom ) {
 
-			case 'mouse':
-			case 'pen':
-				onMouseUp( event );
-				break;
+					_keyState = STATE.ZOOM;
 
-			// TODO touch
+				} else if ( event.code === scope.keys[ STATE.PAN ] && ! scope.noPan ) {
 
-		}
+					_keyState = STATE.PAN;
 
-	}
+				}
 
-	function keydown( event ) {
+			}
 
-		if ( scope.enabled === false ) return;
+			function keyup() {
 
-		window.removeEventListener( 'keydown', keydown );
+				if ( scope.enabled === false ) return;
+				_keyState = STATE.NONE;
+				window.addEventListener( 'keydown', keydown );
 
-		if ( _keyState !== STATE.NONE ) {
+			}
 
-			return;
+			function onMouseDown( event ) {
 
-		} else if ( event.code === scope.keys[ STATE.ROTATE ] && ! scope.noRotate ) {
+				event.preventDefault();
 
-			_keyState = STATE.ROTATE;
+				if ( _state === STATE.NONE ) {
 
-		} else if ( event.code === scope.keys[ STATE.ZOOM ] && ! scope.noZoom ) {
+					switch ( event.button ) {
 
-			_keyState = STATE.ZOOM;
+						case scope.mouseButtons.LEFT:
+							_state = STATE.ROTATE;
+							break;
 
-		} else if ( event.code === scope.keys[ STATE.PAN ] && ! scope.noPan ) {
+						case scope.mouseButtons.MIDDLE:
+							_state = STATE.ZOOM;
+							break;
 
-			_keyState = STATE.PAN;
+						case scope.mouseButtons.RIGHT:
+							_state = STATE.PAN;
+							break;
 
-		}
+						default:
+							_state = STATE.NONE;
 
-	}
+					}
 
-	function keyup() {
+				}
 
-		if ( scope.enabled === false ) return;
+				const state = _keyState !== STATE.NONE ? _keyState : _state;
 
-		_keyState = STATE.NONE;
+				if ( state === STATE.ROTATE && ! scope.noRotate ) {
 
-		window.addEventListener( 'keydown', keydown );
+					_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
 
-	}
+					_movePrev.copy( _moveCurr );
 
-	function onMouseDown( event ) {
+				} else if ( state === STATE.ZOOM && ! scope.noZoom ) {
 
-		event.preventDefault();
+					_zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
 
-		if ( _state === STATE.NONE ) {
+					_zoomEnd.copy( _zoomStart );
 
-			switch ( event.button ) {
+				} else if ( state === STATE.PAN && ! scope.noPan ) {
 
-				case scope.mouseButtons.LEFT:
-					_state = STATE.ROTATE;
-					break;
+					_panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
 
-				case scope.mouseButtons.MIDDLE:
-					_state = STATE.ZOOM;
-					break;
+					_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 );
 
 			}
 
-		}
+			function onMouseMove( event ) {
 
-		var state = ( _keyState !== STATE.NONE ) ? _keyState : _state;
+				if ( scope.enabled === false ) return;
+				event.preventDefault();
+				const state = _keyState !== STATE.NONE ? _keyState : _state;
 
-		if ( state === STATE.ROTATE && ! scope.noRotate ) {
+				if ( state === STATE.ROTATE && ! scope.noRotate ) {
 
-			_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
-			_movePrev.copy( _moveCurr );
+					_movePrev.copy( _moveCurr );
 
-		} else if ( state === STATE.ZOOM && ! scope.noZoom ) {
+					_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
 
-			_zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
-			_zoomEnd.copy( _zoomStart );
+				} else if ( state === STATE.ZOOM && ! scope.noZoom ) {
 
-		} else if ( state === STATE.PAN && ! scope.noPan ) {
+					_zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
 
-			_panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
-			_panEnd.copy( _panStart );
-
-		}
+				} else if ( state === STATE.PAN && ! scope.noPan ) {
 
-		scope.domElement.ownerDocument.addEventListener( 'pointermove', onPointerMove );
-		scope.domElement.ownerDocument.addEventListener( 'pointerup', onPointerUp );
+					_panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
 
-		scope.dispatchEvent( startEvent );
-
-	}
-
-	function onMouseMove( event ) {
+				}
 
-		if ( scope.enabled === false ) return;
+			}
 
-		event.preventDefault();
+			function onMouseUp( event ) {
 
-		var state = ( _keyState !== STATE.NONE ) ? _keyState : _state;
+				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 );
 
-		if ( state === STATE.ROTATE && ! scope.noRotate ) {
+			}
 
-			_movePrev.copy( _moveCurr );
-			_moveCurr.copy( getMouseOnCircle( event.pageX, event.pageY ) );
+			function mousewheel( event ) {
 
-		} else if ( state === STATE.ZOOM && ! scope.noZoom ) {
+				if ( scope.enabled === false ) return;
+				if ( scope.noZoom === true ) return;
+				event.preventDefault();
 
-			_zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
+				switch ( event.deltaMode ) {
 
-		} else if ( state === STATE.PAN && ! scope.noPan ) {
+					case 2:
+						// Zoom in pages
+						_zoomStart.y -= event.deltaY * 0.025;
+						break;
 
-			_panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
+					case 1:
+						// Zoom in lines
+						_zoomStart.y -= event.deltaY * 0.01;
+						break;
 
-		}
+					default:
+						// undefined, 0, assume pixels
+						_zoomStart.y -= event.deltaY * 0.00025;
+						break;
 
-	}
+				}
 
-	function onMouseUp( event ) {
+				scope.dispatchEvent( _startEvent );
+				scope.dispatchEvent( _endEvent );
 
-		if ( scope.enabled === false ) return;
+			}
 
-		event.preventDefault();
+			function touchstart( event ) {
 
-		_state = STATE.NONE;
+				if ( scope.enabled === false ) return;
+				event.preventDefault();
 
-		scope.domElement.ownerDocument.removeEventListener( 'pointermove', onPointerMove );
-		scope.domElement.ownerDocument.removeEventListener( 'pointerup', onPointerUp );
+				switch ( event.touches.length ) {
 
-		scope.dispatchEvent( endEvent );
+					case 1:
+						_state = STATE.TOUCH_ROTATE;
 
-	}
+						_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
 
-	function mousewheel( event ) {
+						_movePrev.copy( _moveCurr );
 
-		if ( scope.enabled === false ) return;
+						break;
 
-		if ( scope.noZoom === true ) return;
+					default:
+						// 2 or more
+						_state = STATE.TOUCH_ZOOM_PAN;
+						const dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+						const dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+						_touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
+						const x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
+						const y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
 
-		event.preventDefault();
+						_panStart.copy( getMouseOnScreen( x, y ) );
 
-		switch ( event.deltaMode ) {
+						_panEnd.copy( _panStart );
 
-			case 2:
-				// Zoom in pages
-				_zoomStart.y -= event.deltaY * 0.025;
-				break;
+						break;
 
-			case 1:
-				// Zoom in lines
-				_zoomStart.y -= event.deltaY * 0.01;
-				break;
+				}
 
-			default:
-				// undefined, 0, assume pixels
-				_zoomStart.y -= event.deltaY * 0.00025;
-				break;
+				scope.dispatchEvent( _startEvent );
 
-		}
+			}
 
-		scope.dispatchEvent( startEvent );
-		scope.dispatchEvent( endEvent );
+			function touchmove( event ) {
 
-	}
+				if ( scope.enabled === false ) return;
+				event.preventDefault();
 
-	function touchstart( event ) {
+				switch ( event.touches.length ) {
 
-		if ( scope.enabled === false ) return;
+					case 1:
+						_movePrev.copy( _moveCurr );
 
-		event.preventDefault();
+						_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
 
-		switch ( event.touches.length ) {
+						break;
 
-			case 1:
-				_state = STATE.TOUCH_ROTATE;
-				_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
-				_movePrev.copy( _moveCurr );
-				break;
+					default:
+						// 2 or more
+						const dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+						const dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+						_touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy );
+						const x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
+						const y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
 
-			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 );
+						_panEnd.copy( getMouseOnScreen( x, y ) );
 
-				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;
 
-		}
+				}
 
-		scope.dispatchEvent( startEvent );
+			}
 
-	}
+			function touchend( event ) {
 
-	function touchmove( event ) {
+				if ( scope.enabled === false ) return;
 
-		if ( scope.enabled === false ) return;
+				switch ( event.touches.length ) {
 
-		event.preventDefault();
+					case 0:
+						_state = STATE.NONE;
+						break;
 
-		switch ( event.touches.length ) {
+					case 1:
+						_state = STATE.TOUCH_ROTATE;
 
-			case 1:
-				_movePrev.copy( _moveCurr );
-				_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
-				break;
+						_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
 
-			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 );
+						_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;
-				_panEnd.copy( getMouseOnScreen( x, y ) );
-				break;
+						break;
 
-		}
+				}
 
-	}
+				scope.dispatchEvent( _endEvent );
 
-	function touchend( event ) {
+			}
 
-		if ( scope.enabled === false ) return;
+			function contextmenu( event ) {
 
-		switch ( event.touches.length ) {
+				if ( scope.enabled === false ) return;
+				event.preventDefault();
 
-			case 0:
-				_state = STATE.NONE;
-				break;
+			}
 
-			case 1:
-				_state = STATE.TOUCH_ROTATE;
-				_moveCurr.copy( getMouseOnCircle( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
-				_movePrev.copy( _moveCurr );
-				break;
+			this.dispose = function () {
+
+				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.addEventListener( 'contextmenu', contextmenu );
+			this.domElement.addEventListener( 'pointerdown', onPointerDown );
+			this.domElement.addEventListener( 'wheel', mousewheel, {
+				passive: false
+			} );
+			this.domElement.addEventListener( 'touchstart', touchstart, {
+				passive: false
+			} );
+			this.domElement.addEventListener( 'touchend', touchend );
+			this.domElement.addEventListener( 'touchmove', touchmove, {
+				passive: false
+			} );
+			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.update();
 
 		}
 
-		scope.dispatchEvent( endEvent );
-
 	}
 
-	function contextmenu( event ) {
-
-		if ( scope.enabled === false ) return;
-
-		event.preventDefault();
-
-	}
-
-	this.dispose = function () {
-
-		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.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.update();
-
-};
+	THREE.TrackballControls = TrackballControls;
 
-THREE.TrackballControls.prototype = Object.create( THREE.EventDispatcher.prototype );
-THREE.TrackballControls.prototype.constructor = THREE.TrackballControls;
+} )();

+ 869 - 1061
examples/js/controls/TransformControls.js

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

+ 200 - 270
examples/js/curves/CurveExtras.js

@@ -1,4 +1,6 @@
-/**
+( function () {
+
+	/**
  * A bunch of parametric curves
  *
  * Formulas collected from various sources
@@ -9,402 +11,328 @@
  * http://www.mi.sanu.ac.rs/vismath/taylorapril2011/Taylor.pdf
  * 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 ) {
+	class GrannyKnot extends THREE.Curve {
 
-		THREE.Curve.call( this );
+		getPoint( t, optionalTarget = new THREE.Vector3() ) {
 
-		this.scale = ( scale === undefined ) ? 70 : scale;
+			const point = optionalTarget;
+			t = 2 * Math.PI * t;
+			const x = - 0.22 * Math.cos( t ) - 1.28 * Math.sin( t ) - 0.44 * Math.cos( 3 * t ) - 0.78 * Math.sin( 3 * t );
+			const 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 );
+			const z = 0.7 * Math.cos( 3 * t ) - 0.4 * Math.sin( 3 * t );
+			return point.set( x, y, z ).multiplyScalar( 20 );
 
-	}
-
-	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 );
+	} // HeartCurve
 
-		return point.set( x, y, z );
 
-	};
+	class HeartCurve extends THREE.Curve {
 
-	// KnotCurve
+		constructor( scale = 5 ) {
 
-	function KnotCurve() {
+			super();
+			this.scale = scale;
 
-		THREE.Curve.call( this );
+		}
 
-	}
+		getPoint( t, optionalTarget = new THREE.Vector3() ) {
 
-	KnotCurve.prototype = Object.create( THREE.Curve.prototype );
-	KnotCurve.prototype.constructor = KnotCurve;
+			const point = optionalTarget;
+			t *= 2 * Math.PI;
+			const x = 16 * Math.pow( Math.sin( t ), 3 );
+			const y = 13 * Math.cos( t ) - 5 * Math.cos( 2 * t ) - 2 * Math.cos( 3 * t ) - Math.cos( 4 * t );
+			const z = 0;
+			return point.set( x, y, z ).multiplyScalar( this.scale );
 
-	KnotCurve.prototype.getPoint = function ( t, optionalTarget ) {
+		}
 
-		var point = optionalTarget || new THREE.Vector3();
+	} // Viviani's THREE.Curve
 
-		t *= 2 * Math.PI;
 
-		var R = 10;
-		var s = 50;
+	class VivianiCurve extends THREE.Curve {
 
-		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 ) );
+		constructor( scale = 70 ) {
 
-		return point.set( x, y, z );
+			super();
+			this.scale = scale;
 
-	};
+		}
 
-	// HelixCurve
+		getPoint( t, optionalTarget = new THREE.Vector3() ) {
 
-	function HelixCurve() {
+			const point = optionalTarget;
+			t = t * 4 * Math.PI; // normalized to 0..1
 
-		THREE.Curve.call( this );
+			const a = this.scale / 2;
+			const x = a * ( 1 + Math.cos( t ) );
+			const y = a * Math.sin( t );
+			const z = 2 * a * Math.sin( t / 2 );
+			return point.set( x, y, z );
 
-	}
+		}
 
-	HelixCurve.prototype = Object.create( THREE.Curve.prototype );
-	HelixCurve.prototype.constructor = HelixCurve;
+	} // KnotCurve
 
-	HelixCurve.prototype.getPoint = function ( t, optionalTarget ) {
 
-		var point = optionalTarget || new THREE.Vector3();
+	class KnotCurve extends THREE.Curve {
 
-		var a = 30; // radius
-		var b = 150; // height
+		getPoint( t, optionalTarget = new THREE.Vector3() ) {
 
-		var t2 = 2 * Math.PI * t * b / 30;
+			const point = optionalTarget;
+			t *= 2 * Math.PI;
+			const R = 10;
+			const s = 50;
+			const x = s * Math.sin( t );
+			const y = Math.cos( t ) * ( R + s * Math.cos( t ) );
+			const z = Math.sin( t ) * ( R + s * Math.cos( t ) );
+			return point.set( x, y, z );
 
-		var x = Math.cos( t2 ) * a;
-		var y = Math.sin( t2 ) * a;
-		var z = b * t;
+		}
 
-		return point.set( x, y, z );
+	} // HelixCurve
 
-	};
 
-	// TrefoilKnot
+	class HelixCurve extends THREE.Curve {
 
-	function TrefoilKnot( scale ) {
+		getPoint( t, optionalTarget = new THREE.Vector3() ) {
 
-		THREE.Curve.call( this );
+			const point = optionalTarget;
+			const a = 30; // radius
 
-		this.scale = ( scale === undefined ) ? 10 : scale;
+			const b = 150; // height
 
-	}
+			const t2 = 2 * Math.PI * t * b / 30;
+			const x = Math.cos( t2 ) * a;
+			const y = Math.sin( t2 ) * a;
+			const z = b * t;
+			return point.set( x, y, z );
 
-	TrefoilKnot.prototype = Object.create( THREE.Curve.prototype );
-	TrefoilKnot.prototype.constructor = TrefoilKnot;
+		}
 
-	TrefoilKnot.prototype.getPoint = function ( t, optionalTarget ) {
+	} // TrefoilKnot
 
-		var point = optionalTarget || new THREE.Vector3();
 
-		t *= Math.PI * 2;
+	class TrefoilKnot extends 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 );
+		constructor( scale = 10 ) {
 
-		return point.set( x, y, z ).multiplyScalar( this.scale );
+			super();
+			this.scale = scale;
 
-	};
+		}
 
-	// TorusKnot
+		getPoint( t, optionalTarget = new THREE.Vector3() ) {
 
-	function TorusKnot( scale ) {
+			const point = optionalTarget;
+			t *= Math.PI * 2;
+			const x = ( 2 + Math.cos( 3 * t ) ) * Math.cos( 2 * t );
+			const y = ( 2 + Math.cos( 3 * t ) ) * Math.sin( 2 * t );
+			const z = Math.sin( 3 * t );
+			return point.set( x, y, z ).multiplyScalar( this.scale );
 
-		THREE.Curve.call( this );
+		}
 
-		this.scale = ( scale === undefined ) ? 10 : scale;
+	} // TorusKnot
 
-	}
 
-	TorusKnot.prototype = Object.create( THREE.Curve.prototype );
-	TorusKnot.prototype.constructor = TorusKnot;
+	class TorusKnot extends THREE.Curve {
 
-	TorusKnot.prototype.getPoint = function ( t, optionalTarget ) {
+		constructor( scale = 10 ) {
 
-		var point = optionalTarget || new THREE.Vector3();
+			super();
+			this.scale = scale;
 
-		var p = 3;
-		var q = 4;
+		}
 
-		t *= Math.PI * 2;
+		getPoint( t, optionalTarget = new THREE.Vector3() ) {
 
-		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 );
+			const point = optionalTarget;
+			const p = 3;
+			const q = 4;
+			t *= Math.PI * 2;
+			const x = ( 2 + Math.cos( q * t ) ) * Math.cos( p * t );
+			const y = ( 2 + Math.cos( q * t ) ) * Math.sin( p * t );
+			const z = Math.sin( q * t );
+			return point.set( x, y, z ).multiplyScalar( this.scale );
 
-		return point.set( x, y, z ).multiplyScalar( this.scale );
+		}
 
-	};
+	} // CinquefoilKnot
 
-	// CinquefoilKnot
 
-	function CinquefoilKnot( scale ) {
+	class CinquefoilKnot extends THREE.Curve {
 
-		THREE.Curve.call( this );
+		constructor( scale = 10 ) {
 
-		this.scale = ( scale === undefined ) ? 10 : scale;
+			super();
+			this.scale = scale;
 
-	}
+		}
 
-	CinquefoilKnot.prototype = Object.create( THREE.Curve.prototype );
-	CinquefoilKnot.prototype.constructor = CinquefoilKnot;
+		getPoint( t, optionalTarget = new THREE.Vector3() ) {
 
-	CinquefoilKnot.prototype.getPoint = function ( t, optionalTarget ) {
+			const point = optionalTarget;
+			const p = 2;
+			const q = 5;
+			t *= Math.PI * 2;
+			const x = ( 2 + Math.cos( q * t ) ) * Math.cos( p * t );
+			const y = ( 2 + Math.cos( q * t ) ) * Math.sin( p * t );
+			const z = Math.sin( q * t );
+			return point.set( x, y, z ).multiplyScalar( this.scale );
 
-		var point = optionalTarget || new THREE.Vector3();
+		}
 
-		var p = 2;
-		var q = 5;
+	} // TrefoilPolynomialKnot
 
-		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 );
+	class TrefoilPolynomialKnot extends THREE.Curve {
 
-		return point.set( x, y, z ).multiplyScalar( this.scale );
+		constructor( scale = 10 ) {
 
-	};
+			super();
+			this.scale = scale;
 
-	// TrefoilPolynomialKnot
+		}
 
-	function TrefoilPolynomialKnot( scale ) {
+		getPoint( t, optionalTarget = new THREE.Vector3() ) {
 
-		THREE.Curve.call( this );
+			const point = optionalTarget;
+			t = t * 4 - 2;
+			const x = Math.pow( t, 3 ) - 3 * t;
+			const y = Math.pow( t, 4 ) - 4 * t * t;
+			const z = 1 / 5 * Math.pow( t, 5 ) - 2 * t;
+			return point.set( x, y, z ).multiplyScalar( this.scale );
 
-		this.scale = ( scale === undefined ) ? 10 : scale;
+		}
 
 	}
 
-	TrefoilPolynomialKnot.prototype = Object.create( THREE.Curve.prototype );
-	TrefoilPolynomialKnot.prototype.constructor = TrefoilPolynomialKnot;
-
-	TrefoilPolynomialKnot.prototype.getPoint = function ( t, optionalTarget ) {
-
-		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;
+	function scaleTo( x, y, t ) {
 
-		return point.set( x, y, z ).multiplyScalar( this.scale );
-
-	};
-
-	var scaleTo = function ( x, y, t ) {
-
-		var r = y - x;
+		const r = y - x;
 		return t * r + x;
 
-	};
-
-	// FigureEightPolynomialKnot
-
-	function FigureEightPolynomialKnot( scale ) {
-
-		THREE.Curve.call( this );
-
-		this.scale = ( scale === undefined ) ? 1 : scale;
-
-	}
-
-	FigureEightPolynomialKnot.prototype = Object.create( THREE.Curve.prototype );
-	FigureEightPolynomialKnot.prototype.constructor = FigureEightPolynomialKnot;
-
-	FigureEightPolynomialKnot.prototype.getPoint = function ( t, optionalTarget ) {
+	} // FigureEightPolynomialKnot
 
-		var point = optionalTarget || new THREE.Vector3();
 
-		t = scaleTo( - 4, 4, t );
+	class FigureEightPolynomialKnot extends THREE.Curve {
 
-		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 );
+		constructor( scale = 1 ) {
 
-		return point.set( x, y, z ).multiplyScalar( this.scale );
-
-	};
+			super();
+			this.scale = scale;
 
-	// DecoratedTorusKnot4a
+		}
 
-	function DecoratedTorusKnot4a( scale ) {
+		getPoint( t, optionalTarget = new THREE.Vector3() ) {
 
-		THREE.Curve.call( this );
+			const point = optionalTarget;
+			t = scaleTo( - 4, 4, t );
+			const x = 2 / 5 * t * ( t * t - 7 ) * ( t * t - 10 );
+			const y = Math.pow( t, 4 ) - 13 * t * t;
+			const z = 1 / 10 * t * ( t * t - 4 ) * ( t * t - 9 ) * ( t * t - 12 );
+			return point.set( x, y, z ).multiplyScalar( this.scale );
 
-		this.scale = ( scale === undefined ) ? 40 : scale;
+		}
 
-	}
+	} // DecoratedTorusKnot4a
 
-	DecoratedTorusKnot4a.prototype = Object.create( THREE.Curve.prototype );
-	DecoratedTorusKnot4a.prototype.constructor = DecoratedTorusKnot4a;
 
-	DecoratedTorusKnot4a.prototype.getPoint = function ( t, optionalTarget ) {
+	class DecoratedTorusKnot4a extends THREE.Curve {
 
-		var point = optionalTarget || new THREE.Vector3();
+		constructor( scale = 40 ) {
 
-		t *= Math.PI * 2;
+			super();
+			this.scale = 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 );
+		getPoint( t, optionalTarget = new THREE.Vector3() ) {
 
-	};
+			const point = optionalTarget;
+			t *= Math.PI * 2;
+			const x = Math.cos( 2 * t ) * ( 1 + 0.6 * ( Math.cos( 5 * t ) + 0.75 * Math.cos( 10 * t ) ) );
+			const y = Math.sin( 2 * t ) * ( 1 + 0.6 * ( Math.cos( 5 * t ) + 0.75 * Math.cos( 10 * t ) ) );
+			const z = 0.35 * Math.sin( 5 * t );
+			return point.set( x, y, z ).multiplyScalar( this.scale );
 
-	// DecoratedTorusKnot4b
+		}
 
-	function DecoratedTorusKnot4b( scale ) {
+	} // DecoratedTorusKnot4b
 
-		THREE.Curve.call( this );
 
-		this.scale = ( scale === undefined ) ? 40 : scale;
+	class DecoratedTorusKnot4b extends THREE.Curve {
 
-	}
+		constructor( scale = 40 ) {
 
-	DecoratedTorusKnot4b.prototype = Object.create( THREE.Curve.prototype );
-	DecoratedTorusKnot4b.prototype.constructor = DecoratedTorusKnot4b;
+			super();
+			this.scale = scale;
 
-	DecoratedTorusKnot4b.prototype.getPoint = function ( t, optionalTarget ) {
+		}
 
-		var point = optionalTarget || new THREE.Vector3();
+		getPoint( t, optionalTarget = new THREE.Vector3() ) {
 
-		var fi = t * Math.PI * 2;
+			const point = optionalTarget;
+			const fi = t * Math.PI * 2;
+			const x = Math.cos( 2 * fi ) * ( 1 + 0.45 * Math.cos( 3 * fi ) + 0.4 * Math.cos( 9 * fi ) );
+			const y = Math.sin( 2 * fi ) * ( 1 + 0.45 * Math.cos( 3 * fi ) + 0.4 * Math.cos( 9 * fi ) );
+			const z = 0.2 * Math.sin( 9 * fi );
+			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 );
+		}
 
-		return point.set( x, y, z ).multiplyScalar( this.scale );
+	} // DecoratedTorusKnot5a
 
-	};
 
-	// DecoratedTorusKnot5a
+	class DecoratedTorusKnot5a extends THREE.Curve {
 
-	function DecoratedTorusKnot5a( scale ) {
+		constructor( scale = 40 ) {
 
-		THREE.Curve.call( this );
+			super();
+			this.scale = scale;
 
-		this.scale = ( scale === undefined ) ? 40 : scale;
+		}
 
-	}
+		getPoint( t, optionalTarget = new THREE.Vector3() ) {
 
-	DecoratedTorusKnot5a.prototype = Object.create( THREE.Curve.prototype );
-	DecoratedTorusKnot5a.prototype.constructor = DecoratedTorusKnot5a;
+			const point = optionalTarget;
+			const fi = t * Math.PI * 2;
+			const x = Math.cos( 3 * fi ) * ( 1 + 0.3 * Math.cos( 5 * fi ) + 0.5 * Math.cos( 10 * fi ) );
+			const y = Math.sin( 3 * fi ) * ( 1 + 0.3 * Math.cos( 5 * fi ) + 0.5 * Math.cos( 10 * fi ) );
+			const z = 0.2 * Math.sin( 20 * fi );
+			return point.set( x, y, z ).multiplyScalar( this.scale );
 
-	DecoratedTorusKnot5a.prototype.getPoint = function ( t, optionalTarget ) {
+		}
 
-		var point = optionalTarget || new THREE.Vector3();
+	} // DecoratedTorusKnot5c
 
-		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 );
+	class DecoratedTorusKnot5c extends THREE.Curve {
 
-		return point.set( x, y, z ).multiplyScalar( this.scale );
+		constructor( scale = 40 ) {
 
-	};
+			super();
+			this.scale = scale;
 
-	// DecoratedTorusKnot5c
+		}
 
-	function DecoratedTorusKnot5c( scale ) {
+		getPoint( t, optionalTarget = new THREE.Vector3() ) {
 
-		THREE.Curve.call( this );
+			const point = optionalTarget;
+			const fi = t * Math.PI * 2;
+			const x = Math.cos( 4 * fi ) * ( 1 + 0.5 * ( Math.cos( 5 * fi ) + 0.4 * Math.cos( 20 * fi ) ) );
+			const y = Math.sin( 4 * fi ) * ( 1 + 0.5 * ( Math.cos( 5 * fi ) + 0.4 * Math.cos( 20 * fi ) ) );
+			const z = 0.35 * Math.sin( 15 * fi );
+			return point.set( x, y, z ).multiplyScalar( this.scale );
 
-		this.scale = ( scale === undefined ) ? 40 : scale;
+		}
 
 	}
 
-	DecoratedTorusKnot5c.prototype = Object.create( THREE.Curve.prototype );
-	DecoratedTorusKnot5c.prototype.constructor = DecoratedTorusKnot5c;
-
-	DecoratedTorusKnot5c.prototype.getPoint = function ( t, optionalTarget ) {
-
-		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 );
-
-	};
-
-	return {
+	const Curves = {
 		GrannyKnot: GrannyKnot,
 		HeartCurve: HeartCurve,
 		VivianiCurve: VivianiCurve,
@@ -421,4 +349,6 @@ THREE.Curves = ( function () {
 		DecoratedTorusKnot5c: DecoratedTorusKnot5c
 	};
 
+	THREE.Curves = Curves;
+
 } )();

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

@@ -1,66 +1,75 @@
-/**
+( 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 ) {
+	class NURBSCurve extends THREE.Curve {
 
-		// ensure Vector4 for control points
-		var point = controlPoints[ i ];
-		this.controlPoints[ i ] = new THREE.Vector4( point.x, point.y, point.z, point.w );
+		constructor( degree, knots
+			/* array of reals */
+			, controlPoints
+			/* array of Vector(2|3|4) */
+			, startKnot
+			/* index in knots */
+			, endKnot
+			/* index in knots */
+		) {
 
-	}
+			super();
+			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 ( let 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
+				const 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();
+		getPoint( t, optionalTarget = new THREE.Vector3() ) {
 
-	var u = this.knots[ this.startKnot ] + t * ( this.knots[ this.endKnot ] - this.knots[ this.startKnot ] ); // linear mapping t->u
+			const point = optionalTarget;
+			const 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
 
-	// following results in (wx, wy, wz, w) homogeneous point
-	var hpoint = THREE.NURBSUtils.calcBSplinePoint( this.degree, this.knots, this.controlPoints, u );
+			const hpoint = THREE.NURBSUtils.calcBSplinePoint( this.degree, this.knots, this.controlPoints, u );
 
-	if ( hpoint.w != 1.0 ) {
+			if ( hpoint.w !== 1.0 ) {
 
-		// project to 3D space: (wx, wy, wz, w) -> (x, y, z, 1)
-		hpoint.divideScalar( hpoint.w );
+				// 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 );
 
-};
+		}
 
+		getTangent( t, optionalTarget = new THREE.Vector3() ) {
 
-THREE.NURBSCurve.prototype.getTangent = function ( t, optionalTarget ) {
+			const tangent = optionalTarget;
+			const u = this.knots[ 0 ] + t * ( this.knots[ this.knots.length - 1 ] - this.knots[ 0 ] );
+			const ders = THREE.NURBSUtils.calcNURBSDerivatives( this.degree, this.knots, this.controlPoints, u, 1 );
+			tangent.copy( ders[ 1 ] ).normalize();
+			return tangent;
 
-	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;
+	THREE.NURBSCurve = NURBSCurve;
 
-};
+} )();

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

@@ -1,46 +1,54 @@
-/**
+( 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) */ ) {
+	class NURBSSurface {
 
-	this.degree1 = degree1;
-	this.degree2 = degree2;
-	this.knots1 = knots1;
-	this.knots2 = knots2;
-	this.controlPoints = [];
+		constructor( degree1, degree2, knots1, knots2
+			/* arrays of reals */
+			, controlPoints
+			/* array^2 of Vector(2|3|4) */
+		) {
 
-	var len1 = knots1.length - degree1 - 1;
-	var len2 = knots2.length - degree2 - 1;
+			this.degree1 = degree1;
+			this.degree2 = degree2;
+			this.knots1 = knots1;
+			this.knots2 = knots2;
+			this.controlPoints = [];
+			const len1 = knots1.length - degree1 - 1;
+			const len2 = knots2.length - degree2 - 1; // ensure THREE.Vector4 for control points
 
-	// ensure Vector4 for control points
-	for ( var i = 0; i < len1; ++ i ) {
+			for ( let i = 0; i < len1; ++ i ) {
 
-		this.controlPoints[ i ] = [];
-		for ( var j = 0; j < len2; ++ j ) {
+				this.controlPoints[ i ] = [];
 
-			var point = controlPoints[ i ][ j ];
-			this.controlPoints[ i ][ j ] = new THREE.Vector4( point.x, point.y, point.z, point.w );
+				for ( let j = 0; j < len2; ++ j ) {
 
-		}
+					const point = controlPoints[ i ][ j ];
+					this.controlPoints[ i ][ j ] = new THREE.Vector4( point.x, point.y, point.z, point.w );
 
-	}
+				}
 
-};
+			}
 
+		}
 
-THREE.NURBSSurface.prototype = {
+		getPoint( t1, t2, target ) {
 
-	constructor: THREE.NURBSSurface,
+			const u = this.knots1[ 0 ] + t1 * ( this.knots1[ this.knots1.length - 1 ] - this.knots1[ 0 ] ); // linear mapping t1->u
 
-	getPoint: function ( t1, t2, target ) {
+			const v = this.knots2[ 0 ] + t2 * ( this.knots2[ this.knots2.length - 1 ] - this.knots2[ 0 ] ); // linear mapping t2->u
 
-		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.NURBSUtils.calcSurfacePoint( this.degree1, this.degree2, this.knots1, this.knots2, this.controlPoints, u, v, target );
+		}
 
 	}
-};
+
+	THREE.NURBSSurface = NURBSSurface;
+
+} )();

+ 272 - 289
examples/js/curves/NURBSUtils.js

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

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

@@ -0,0 +1,1656 @@
+( function () {
+
+	const _m1 = new THREE.Matrix4();
+
+	const _obj = new THREE.Object3D();
+
+	const _offset = new THREE.Vector3();
+
+	class Geometry extends THREE.EventDispatcher {
+
+		constructor() {
+
+			super();
+			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;
+
+		}
+
+		applyMatrix4( 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( angle ) {
+
+			// rotate geometry around world x-axis
+			_m1.makeRotationX( angle );
+
+			this.applyMatrix4( _m1 );
+			return this;
+
+		}
+
+		rotateY( angle ) {
+
+			// rotate geometry around world y-axis
+			_m1.makeRotationY( angle );
+
+			this.applyMatrix4( _m1 );
+			return this;
+
+		}
+
+		rotateZ( angle ) {
+
+			// rotate geometry around world z-axis
+			_m1.makeRotationZ( angle );
+
+			this.applyMatrix4( _m1 );
+			return this;
+
+		}
+
+		translate( x, y, z ) {
+
+			// translate geometry
+			_m1.makeTranslation( x, y, z );
+
+			this.applyMatrix4( _m1 );
+			return this;
+
+		}
+
+		scale( x, y, z ) {
+
+			// scale geometry
+			_m1.makeScale( x, y, z );
+
+			this.applyMatrix4( _m1 );
+			return this;
+
+		}
+
+		lookAt( vector ) {
+
+			_obj.lookAt( vector );
+
+			_obj.updateMatrix();
+
+			this.applyMatrix4( _obj.matrix );
+			return this;
+
+		}
+
+		fromBufferGeometry( 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() {
+
+			this.computeBoundingBox();
+			this.boundingBox.getCenter( _offset ).negate();
+			this.translate( _offset.x, _offset.y, _offset.z );
+			return this;
+
+		}
+
+		normalize() {
+
+			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() {
+
+			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( 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() {
+
+			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() {
+
+			// 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() {
+
+			if ( this.boundingBox === null ) {
+
+				this.boundingBox = new THREE.Box3();
+
+			}
+
+			this.boundingBox.setFromPoints( this.vertices );
+
+		}
+
+		computeBoundingSphere() {
+
+			if ( this.boundingSphere === null ) {
+
+				this.boundingSphere = new THREE.Sphere();
+
+			}
+
+			this.boundingSphere.setFromPoints( this.vertices );
+
+		}
+
+		merge( 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( 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( 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( 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() {
+
+			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() {
+
+			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() {
+
+			/*
+     // 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( 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() {
+
+			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() {
+
+			console.error( 'THREE.Geometry: .computeTangents() has been removed.' );
+
+		}
+
+		computeLineDistances() {
+
+			console.error( 'THREE.Geometry: .computeLineDistances() has been removed. Use THREE.Line.computeLineDistances() instead.' );
+
+		}
+
+		applyMatrix( matrix ) {
+
+			console.warn( 'THREE.Geometry: .applyMatrix() has been renamed to .applyMatrix4().' );
+			return this.applyMatrix4( matrix );
+
+		}
+
+		dispose() {
+
+			this.dispatchEvent( {
+				type: 'dispose'
+			} );
+
+		}
+
+		static createBufferGeometryFromObject( 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;
+
+		}
+
+	}
+
+	Geometry.prototype.isGeometry = true;
+
+	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;
+
+} )();

+ 68 - 122
examples/js/effects/AnaglyphEffect.js

@@ -1,151 +1,97 @@
-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
+	class AnaglyphEffect {
 
-	this.colorMatrixLeft = new THREE.Matrix3().fromArray( [
-		0.456100, - 0.0400822, - 0.0152161,
-		0.500484, - 0.0378246, - 0.0205971,
-		0.176381, - 0.0157589, - 0.00546856
-	] );
+		constructor( renderer, width = 512, height = 512 ) {
 
-	this.colorMatrixRight = new THREE.Matrix3().fromArray( [
-		- 0.0434706, 0.378476, - 0.0721527,
-		- 0.0879388, 0.73364, - 0.112961,
-		- 0.00155529, - 0.0184503, 1.2264
-	] );
+			// Dubois matrices from https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.7.6968&rep=rep1&type=pdf#page=4
+			this.colorMatrixLeft = new THREE.Matrix3().fromArray( [ 0.456100, - 0.0400822, - 0.0152161, 0.500484, - 0.0378246, - 0.0205971, 0.176381, - 0.0157589, - 0.00546856 ] );
+			this.colorMatrixRight = new THREE.Matrix3().fromArray( [ - 0.0434706, 0.378476, - 0.0721527, - 0.0879388, 0.73364, - 0.112961, - 0.00155529, - 0.0184503, 1.2264 ] );
 
-	var _camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
+			const _camera = new THREE.OrthographicCamera( - 1, 1, 1, - 1, 0, 1 );
 
-	var _scene = new THREE.Scene();
+			const _scene = new THREE.Scene();
 
-	var _stereo = new THREE.StereoCamera();
+			const _stereo = new THREE.StereoCamera();
 
-	var _params = { minFilter: THREE.LinearFilter, magFilter: THREE.NearestFilter, format: THREE.RGBAFormat };
+			const _params = {
+				minFilter: THREE.LinearFilter,
+				magFilter: THREE.NearestFilter,
+				format: THREE.RGBAFormat
+			};
 
-	if ( width === undefined ) width = 512;
-	if ( height === undefined ) height = 512;
+			const _renderTargetL = new THREE.WebGLRenderTarget( width, height, _params );
 
-	var _renderTargetL = new THREE.WebGLRenderTarget( width, height, _params );
-	var _renderTargetR = new THREE.WebGLRenderTarget( width, height, _params );
+			const _renderTargetR = new THREE.WebGLRenderTarget( width, height, _params );
 
-	var _material = new THREE.ShaderMaterial( {
+			const _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' )
+			} );
 
-		uniforms: {
+			const _mesh = new THREE.Mesh( new THREE.PlaneGeometry( 2, 2 ), _material );
 
-			'mapLeft': { value: _renderTargetL.texture },
-			'mapRight': { value: _renderTargetR.texture },
+			_scene.add( _mesh );
 
-			'colorMatrixLeft': { value: this.colorMatrixLeft },
-			'colorMatrixRight': { value: this.colorMatrixRight }
+			this.setSize = function ( width, height ) {
 
-		},
+				renderer.setSize( width, height );
+				const pixelRatio = renderer.getPixelRatio();
 
-		vertexShader: [
+				_renderTargetL.setSize( width * pixelRatio, height * pixelRatio );
 
-			'varying vec2 vUv;',
+				_renderTargetR.setSize( width * pixelRatio, height * pixelRatio );
 
-			'void main() {',
+			};
 
-			'	vUv = vec2( uv.x, uv.y );',
-			'	gl_Position = projectionMatrix * modelViewMatrix * vec4( position, 1.0 );',
+			this.render = function ( scene, camera ) {
 
-			'}'
+				const currentRenderTarget = renderer.getRenderTarget();
+				scene.updateMatrixWorld();
+				if ( camera.parent === null ) camera.updateMatrixWorld();
 
-		].join( '\n' ),
+				_stereo.update( camera );
 
-		fragmentShader: [
+				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 );
 
-			'uniform sampler2D mapLeft;',
-			'uniform sampler2D mapRight;',
-			'varying vec2 vUv;',
+			};
 
-			'uniform mat3 colorMatrixLeft;',
-			'uniform mat3 colorMatrixRight;',
+			this.dispose = function () {
 
-			// These functions implement sRGB linearization and gamma correction
+				if ( _renderTargetL ) _renderTargetL.dispose();
+				if ( _renderTargetR ) _renderTargetR.dispose();
+				if ( _mesh ) _mesh.geometry.dispose();
+				if ( _material ) _material.dispose();
 
-			'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;',
-			'}',
+	}
 
+	THREE.AnaglyphEffect = AnaglyphEffect;
 
-			'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();
-
-	};
-
-};
+} )();

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

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

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

+ 50 - 72
examples/js/effects/ParallaxBarrierEffect.js

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

+ 111 - 87
examples/js/effects/PeppersGhostEffect.js

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

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

@@ -1,44 +1,50 @@
-THREE.StereoEffect = function ( renderer ) {
+( function () {
 
-	var _stereo = new THREE.StereoCamera();
-	_stereo.aspect = 0.5;
-	var size = new THREE.Vector2();
+	class StereoEffect {
 
-	this.setEyeSeparation = function ( eyeSep ) {
+		constructor( renderer ) {
 
-		_stereo.eyeSep = eyeSep;
+			const _stereo = new THREE.StereoCamera();
 
-	};
+			_stereo.aspect = 0.5;
+			const 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;
+
+} )();

+ 279 - 475
examples/js/exporters/ColladaExporter.js

@@ -1,60 +1,53 @@
-/**
+( function () {
+
+	/**
  * https://github.com/gkjohnson/collada-exporter-js
  *
  * Usage:
- *  var exporter = new THREE.ColladaExporter();
+ *  const exporter = new ColladaExporter();
  *
- *  var data = exporter.parse(mesh);
+ *  const data = exporter.parse(mesh);
  *
  * Format Definition:
  *  https://www.khronos.org/collada/
  */
 
-THREE.ColladaExporter = function () {};
+	class ColladaExporter {
 
-THREE.ColladaExporter.prototype = {
+		parse( object, onDone, options = {} ) {
 
-	constructor: THREE.ColladaExporter,
+			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 !== '' ) {
+			const 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 = /<[^>]+>[^<]*<\/[^<]+>/;
+				const IS_END_TAG = /^<\//;
+				const IS_SELF_CLOSING = /(\?>$)|(\/>$)/;
+				const HAS_TEXT = /<[^>]+>[^<]*<\/[^<]+>/;
 
-			var pad = ( ch, num ) => ( num > 0 ? ch + pad( ch, num - 1 ) : '' );
+				const pad = ( ch, num ) => num > 0 ? ch + pad( ch, num - 1 ) : '';
 
-			var tagnum = 0;
-			return urdf
-				.match( /(<[^>]+>[^<]+<\/[^<]+>)|(<[^>]+>)/g )
-				.map( tag => {
+				let 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 +55,7 @@ THREE.ColladaExporter.prototype = {
 
 					}
 
-					var res = `${ pad( '  ', tagnum ) }${ tag }`;
+					const res = `${pad( '  ', tagnum )}${tag}`;
 
 					if ( ! HAS_TEXT.test( tag ) && ! IS_SELF_CLOSING.test( tag ) && ! IS_END_TAG.test( tag ) ) {
 
@@ -72,592 +65,403 @@ 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 ) {
+				const b = atob( str );
+				const buf = new Uint8Array( b.length );
 
-			var b = atob( str );
-			var buf = new Uint8Array( b.length );
+				for ( let 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;
+			let 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' );
+				const 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 );
+			const 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
+					const arr = new attr.array.constructor( attr.count * attr.itemSize );
+					const size = attr.itemSize;
 
-			if ( attr.isInterleavedBufferAttribute ) {
+					for ( let 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 ( let 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>' +
+				const array = attrBufferToArray( attr );
+				const res = `<source id="${name}">` + `<float_array id="${name}-array" count="${array.length}">` + array.join( ' ' ) + '</float_array>' + '<technique_common>' + `<accessor source="#${name}-array" count="${Math.floor( array.length / attr.itemSize )}" stride="${attr.itemSize}">` + params.map( n => `<param name="${n}" type="${type}" />` ).join( '' ) + '</accessor>' + '</technique_common>' + '</source>';
+				return res;
 
-					'<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>';
+			let 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 ) {
 
-		}
+				let 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
+					const 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.' );
+					const meshid = `Mesh${libraryGeometries.length + 1}`;
+					const indexCount = bufferGeometry.index ? bufferGeometry.index.count * bufferGeometry.index.itemSize : bufferGeometry.attributes.position.count;
+					const groups = bufferGeometry.groups != null && bufferGeometry.groups.length !== 0 ? bufferGeometry.groups : [ {
+						start: 0,
+						count: indexCount,
+						materialIndex: 0
+					} ];
+					const gname = g.name ? ` name="${g.name}"` : '';
+					let gnode = `<geometry id="${meshid}"${gname}><mesh>`; // define the geometry node and the vertices for the geometry
 
-				}
+					const posName = `${meshid}-position`;
+					const vertName = `${meshid}-vertices`;
+					gnode += getAttribute( bufferGeometry.attributes.position, posName, [ 'X', 'Y', 'Z' ], 'float' );
+					gnode += `<vertices id="${vertName}"><input semantic="POSITION" source="#${posName}" /></vertices>`; // NOTE: We're not optimizing the attribute arrays here, so they're all the same length and
+					// can therefore share the same triangle indices. However, MeshLab seems to have trouble opening
+					// 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 }`;
+					let 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 } ];
+						const 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/
+						const 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 ) {
+						const 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" />`;
+						const 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 ) {
+					let 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 ( let 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 ( let i = 0, l = groups.length; i < l; i ++ ) {
 
-				for ( var i = 0, l = groups.length; i < l; i ++ ) {
+						const group = groups[ i ];
+						const subarr = subArray( indexArray, group.start, group.count );
+						const polycount = subarr.length / 3;
+						gnode += `<triangles material="MESH_MATERIAL_${group.materialIndex}" count="${polycount}">`;
+						gnode += triangleInputs;
+						gnode += `<p>${subarr.join( ' ' )}</p>`;
+						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 ) {
 
-		}
+				let 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}`;
+					const ext = 'png';
+					const name = tex.name || texid;
+					let 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 );
+				let matid = materialMap.get( m );
 
-			if ( matid == null ) {
+				if ( matid == null ) {
 
-				matid = `Mat${ libraryEffects.length + 1 }`;
+					matid = `Mat${libraryEffects.length + 1}`;
+					let 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.' );
+						}
 
 					}
 
-				}
+					const emissive = m.emissive ? m.emissive : new THREE.Color( 0, 0, 0 );
+					const diffuse = m.color ? m.color : new THREE.Color( 0, 0, 0 );
+					const specular = m.specular ? m.specular : new THREE.Color( 1, 1, 1 );
+					const shininess = m.shininess || 0;
+					const reflectivity = m.reflectivity || 0; // Do not export and alpha map for the reasons mentioned in issue (#13792)
+					// in three.js alpha maps are black and white, but collada expects the alpha
+					// channel to specify the transparency
+
+					let 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>`;
+						}
 
 					}
 
+					const techniqueNode = `<technique sid="common"><${type}>` + '<emission>' + ( m.emissiveMap ? '<texture texture="emissive-sampler" texcoord="TEXCOORD" />' : `<color sid="emission">${emissive.r} ${emissive.g} ${emissive.b} 1</color>` ) + '</emission>' + ( type !== 'constant' ? '<diffuse>' + ( m.map ? '<texture texture="diffuse-sampler" texcoord="TEXCOORD" />' : `<color sid="diffuse">${diffuse.r} ${diffuse.g} ${diffuse.b} 1</color>` ) + '</diffuse>' : '' ) + ( type !== 'constant' ? '<bump>' + ( m.normalMap ? '<texture texture="bump-sampler" texcoord="TEXCOORD" />' : '' ) + '</bump>' : '' ) + ( type === 'phong' ? `<specular><color sid="specular">${specular.r} ${specular.g} ${specular.b} 1</color></specular>` + '<shininess>' + ( m.specularMap ? '<texture texture="specular-sampler" texcoord="TEXCOORD" />' : `<float sid="shininess">${shininess}</float>` ) + '</shininess>' : '' ) + `<reflective><color>${diffuse.r} ${diffuse.g} ${diffuse.b} 1</color></reflective>` + `<reflectivity><float>${reflectivity}</float></reflectivity>` + transparencyNode + `</${type}></technique>`;
+					const effectnode = `<effect id="${matid}-effect">` + '<profile_COMMON>' + ( m.map ? '<newparam sid="diffuse-surface"><surface type="2D">' + `<init_from>${processTexture( m.map )}</init_from>` + '</surface></newparam>' + '<newparam sid="diffuse-sampler"><sampler2D><source>diffuse-surface</source></sampler2D></newparam>' : '' ) + ( m.specularMap ? '<newparam sid="specular-surface"><surface type="2D">' + `<init_from>${processTexture( m.specularMap )}</init_from>` + '</surface></newparam>' + '<newparam sid="specular-sampler"><sampler2D><source>specular-surface</source></sampler2D></newparam>' : '' ) + ( m.emissiveMap ? '<newparam sid="emissive-surface"><surface type="2D">' + `<init_from>${processTexture( m.emissiveMap )}</init_from>` + '</surface></newparam>' + '<newparam sid="emissive-sampler"><sampler2D><source>emissive-surface</source></sampler2D></newparam>' : '' ) + ( m.normalMap ? '<newparam sid="bump-surface"><surface type="2D">' + `<init_from>${processTexture( m.normalMap )}</init_from>` + '</surface></newparam>' + '<newparam sid="bump-sampler"><sampler2D><source>bump-surface</source></sampler2D></newparam>' : '' ) + techniqueNode + ( m.side === THREE.DoubleSide ? '<extra><technique profile="THREEJS"><double_sided sid="double_sided" type="int">1</double_sided></technique></extra>' : '' ) + '</profile_COMMON>' + '</effect>';
+					const materialName = m.name ? ` name="${m.name}"` : '';
+					const materialNode = `<material id="${matid}"${materialName}><instance_effect url="#${matid}-effect" /></material>`;
+					libraryMaterials.push( materialNode );
+					libraryEffects.push( effectnode );
+					materialMap.set( m, matid );
+
 				}
 
-				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 ) {
+				let 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.
+					const geomInfo = processGeometry( o.geometry );
+					const meshid = geomInfo.meshid;
+					const geometry = geomInfo.bufferGeometry; // ids of the materials to bind to the geometry
 
-			if ( o.isMesh === true && o.geometry !== null ) {
+					let matids = null;
+					let matidsArray; // get a list of materials to bind to the sub groups of the geometry.
+					// If the amount of subgroups is greater than the materials, than reuse
+					// the materials.
 
-				// function returns the id associated with the mesh and a "BufferGeometry" version
-				// of the geometry in case it's not a geometry.
-				var geomInfo = processGeometry( o.geometry );
-				var meshid = geomInfo.meshid;
-				var geometry = geomInfo.bufferGeometry;
+					const mat = o.material || new THREE.MeshBasicMaterial();
+					const 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 ) );
-
-			node += '</node>';
+			const geometryInfo = new WeakMap();
+			const materialMap = new WeakMap();
+			const imageMap = new WeakMap();
+			const textures = [];
+			const libraryImages = [];
+			const libraryGeometries = [];
+			const libraryEffects = [];
+			const libraryMaterials = [];
+			const libraryVisualScenes = processObject( object );
+			const specLink = version === '1.4.1' ? 'http://www.collada.org/2005/11/COLLADASchema' : 'https://www.khronos.org/collada/';
+			let dae = '<?xml version="1.0" encoding="UTF-8" standalone="no" ?>' + `<COLLADA xmlns="${specLink}" version="${version}">` + '<asset>' + ( '<contributor>' + '<authoring_tool>three.js Collada Exporter</authoring_tool>' + ( options.author !== null ? `<author>${options.author}</author>` : '' ) + '</contributor>' + `<created>${new Date().toISOString()}</created>` + `<modified>${new Date().toISOString()}</modified>` + '<up_axis>Y_UP</up_axis>' ) + '</asset>';
+			dae += `<library_images>${libraryImages.join( '' )}</library_images>`;
+			dae += `<library_effects>${libraryEffects.join( '' )}</library_effects>`;
+			dae += `<library_materials>${libraryMaterials.join( '' )}</library_materials>`;
+			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>';
+			const res = {
+				data: format( dae ),
+				textures
+			};
+
+			if ( typeof onDone === 'function' ) {
+
+				requestAnimationFrame( () => onDone( res ) );
 
-			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;
+
+} )();

+ 115 - 139
examples/js/exporters/DracoExporter.js

@@ -1,4 +1,6 @@
-/**
+( function () {
+
+	/**
  * Export draco compressed files from threejs geometry objects.
  *
  * Draco files are compressed and usually are smaller than conventional 3D file formats.
@@ -12,237 +14,211 @@
  *  - exportNormals
  */
 
-/* global DracoEncoderModule */
-
-THREE.DRACOExporter = function () {};
-
-THREE.DRACOExporter.prototype = {
-
-	constructor: THREE.DRACOExporter,
-
-	parse: function ( object, options ) {
-
-		if ( object.isBufferGeometry === true ) {
-
-			throw new Error( 'DRACOExporter: The first parameter of parse() is now an instance of Mesh or Points.' );
-
-		}
-
-		if ( DracoEncoderModule === undefined ) {
+	/* global DracoEncoderModule */
+	class DRACOExporter {
 
-			throw new Error( 'THREE.DRACOExporter: required the draco_decoder to work.' );
+		parse( object, options = {
+			decodeSpeed: 5,
+			encodeSpeed: 5,
+			encoderMethod: DRACOExporter.MESH_EDGEBREAKER_ENCODING,
+			quantization: [ 16, 8, 8, 8, 8 ],
+			exportUvs: true,
+			exportNormals: true,
+			exportColor: false
+		} ) {
 
-		}
-
-		if ( options === undefined ) {
-
-			options = {
+			if ( object.isBufferGeometry === true ) {
 
-				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( 'DRACOExporter: The first parameter of parse() is now an instance of Mesh or Points.' );
 
-			};
+			}
 
-		}
+			if ( DracoEncoderModule === undefined ) {
 
-		var geometry = object.geometry;
+				throw new Error( 'THREE.DRACOExporter: required the draco_decoder to work.' );
 
-		var dracoEncoder = DracoEncoderModule();
-		var encoder = new dracoEncoder.Encoder();
-		var builder;
-		var dracoObject;
+			}
 
+			const geometry = object.geometry;
+			const dracoEncoder = DracoEncoderModule();
+			const encoder = new dracoEncoder.Encoder();
+			let builder;
+			let dracoObject;
 
-		if ( geometry.isBufferGeometry !== true ) {
+			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();
+				const vertices = geometry.getAttribute( 'position' );
+				builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.POSITION, vertices.count, vertices.itemSize, vertices.array );
+				const 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 );
+					const faces = new ( vertices.count > 65535 ? Uint32Array : Uint16Array )( vertices.count );
 
-			} else {
+					for ( let 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 ) {
 
-			if ( options.exportNormals === true ) {
+					const normals = geometry.getAttribute( 'normal' );
 
-				var normals = geometry.getAttribute( 'normal' );
+					if ( normals !== undefined ) {
 
-				if ( normals !== undefined ) {
+						builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.NORMAL, normals.count, normals.itemSize, normals.array );
 
-					builder.AddFloatAttributeToMesh( dracoObject, dracoEncoder.NORMAL, normals.count, normals.itemSize, normals.array );
+					}
 
 				}
 
-			}
+				if ( options.exportUvs === true ) {
 
-			if ( options.exportUvs === true ) {
+					const 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 ) {
+					const 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();
+				const 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 ) {
+					const 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 {
-
-			throw new Error( 'DRACOExporter: Unsupported object type.' );
+			} else {
 
-		}
+				throw new Error( 'DRACOExporter: Unsupported object type.' );
 
-		//Compress using draco encoder
+			} //Compress using draco encoder
 
-		var encodedData = new dracoEncoder.DracoInt8Array();
 
-		//Sets the desired encoding and decoding speed for the given options from 0 (slowest speed, but the best compression) to 10 (fastest, but the worst compression).
+			const encodedData = new dracoEncoder.DracoInt8Array(); //Sets the desired encoding and decoding speed for the given options from 0 (slowest speed, but the best compression) to 10 (fastest, but the worst compression).
 
-		var encodeSpeed = ( options.encodeSpeed !== undefined ) ? options.encodeSpeed : 5;
-		var decodeSpeed = ( options.decodeSpeed !== undefined ) ? options.decodeSpeed : 5;
+			const encodeSpeed = options.encodeSpeed !== undefined ? options.encodeSpeed : 5;
+			const decodeSpeed = options.decodeSpeed !== undefined ? options.decodeSpeed : 5;
+			encoder.SetSpeedOptions( encodeSpeed, decodeSpeed ); // Sets the desired encoding method for a given geometry.
 
-		encoder.SetSpeedOptions( encodeSpeed, decodeSpeed );
+			if ( options.encoderMethod !== undefined ) {
 
-		// Sets the desired encoding method for a given geometry.
+				encoder.SetEncodingMethod( options.encoderMethod );
 
-		if ( options.encoderMethod !== undefined ) {
+			} // 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.
 
-			encoder.SetEncodingMethod( options.encoderMethod );
 
-		}
+			if ( options.quantization !== undefined ) {
 
-		// 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 ) {
+				for ( let i = 0; i < 5; i ++ ) {
 
-			for ( var i = 0; i < 5; i ++ ) {
+					if ( options.quantization[ i ] !== undefined ) {
 
-				if ( options.quantization[ i ] !== undefined ) {
+						encoder.SetAttributeQuantization( i, options.quantization[ i ] );
 
-					encoder.SetAttributeQuantization( i, options.quantization[ i ] );
+					}
 
 				}
 
 			}
 
-		}
-
-		var length;
+			let length;
 
-		if ( object.isMesh === true ) {
+			if ( object.isMesh === true ) {
 
-			length = encoder.EncodeMeshToDracoBuffer( dracoObject, encodedData );
+				length = encoder.EncodeMeshToDracoBuffer( dracoObject, encodedData );
 
-		} else {
+			} else {
 
-			length = encoder.EncodePointCloudToDracoBuffer( dracoObject, true, encodedData );
+				length = encoder.EncodePointCloudToDracoBuffer( dracoObject, true, encodedData );
 
-		}
+			}
 
-		dracoEncoder.destroy( dracoObject );
+			dracoEncoder.destroy( dracoObject );
 
-		if ( length === 0 ) {
+			if ( length === 0 ) {
 
-			throw new Error( 'THREE.DRACOExporter: Draco encoding failed.' );
+				throw new Error( 'THREE.DRACOExporter: Draco encoding failed.' );
 
-		}
+			} //Copy encoded data to buffer.
 
-		//Copy encoded data to buffer.
-		var outputData = new Int8Array( new ArrayBuffer( length ) );
 
-		for ( var i = 0; i < length; i ++ ) {
+			const outputData = new Int8Array( new ArrayBuffer( length ) );
 
-			outputData[ i ] = encodedData.GetValue( i );
+			for ( let i = 0; i < length; i ++ ) {
 
-		}
+				outputData[ i ] = encodedData.GetValue( i );
 
-		dracoEncoder.destroy( encodedData );
-		dracoEncoder.destroy( encoder );
-		dracoEncoder.destroy( builder );
+			}
 
-		return outputData;
+			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;
+} )();

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 293 - 354
examples/js/exporters/GLTFExporter.js


+ 120 - 134
examples/js/exporters/MMDExporter.js

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

+ 147 - 173
examples/js/exporters/OBJExporter.js

@@ -1,304 +1,278 @@
-THREE.OBJExporter = function () {};
+( function () {
 
-THREE.OBJExporter.prototype = {
+	class OBJExporter {
 
-	constructor: THREE.OBJExporter,
+		parse( object ) {
 
-	parse: function ( object ) {
+			let output = '';
+			let indexVertex = 0;
+			let indexVertexUvs = 0;
+			let indexNormals = 0;
+			const vertex = new THREE.Vector3();
+			const color = new THREE.Color();
+			const normal = new THREE.Vector3();
+			const uv = new THREE.Vector2();
+			const face = [];
 
-		var output = '';
+			function parseMesh( mesh ) {
 
-		var indexVertex = 0;
-		var indexVertexUvs = 0;
-		var indexNormals = 0;
+				let nbVertex = 0;
+				let nbNormals = 0;
+				let nbVertexUvs = 0;
+				const geometry = mesh.geometry;
+				const 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;
+				const vertices = geometry.getAttribute( 'position' );
+				const normals = geometry.getAttribute( 'normal' );
+				const uvs = geometry.getAttribute( 'uv' );
+				const 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';
 
-			}
-
-			// 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';
+				} // vertices
 
-			// name of the mesh material
-			if ( mesh.material && mesh.material.name ) {
 
-				output += 'usemtl ' + mesh.material.name + '\n';
+				if ( vertices !== undefined ) {
 
-			}
-
-			// vertices
+					for ( let i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) {
 
-			if ( vertices !== undefined ) {
+						vertex.x = vertices.getX( i );
+						vertex.y = vertices.getY( i );
+						vertex.z = vertices.getZ( i ); // transform the vertex to world space
 
-				for ( i = 0, l = vertices.count; i < l; i ++, nbVertex ++ ) {
+						vertex.applyMatrix4( mesh.matrixWorld ); // transform the vertex to export format
 
-					vertex.x = vertices.getX( i );
-					vertex.y = vertices.getY( i );
-					vertex.z = vertices.getZ( i );
+						output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n';
 
-					// transform the vertex to world space
-					vertex.applyMatrix4( mesh.matrixWorld );
+					}
 
-					// transform the vertex to export format
-					output += 'v ' + vertex.x + ' ' + vertex.y + ' ' + vertex.z + '\n';
+				} // uvs
 
-				}
 
-			}
+				if ( uvs !== undefined ) {
 
-			// uvs
+					for ( let i = 0, l = uvs.count; i < l; i ++, nbVertexUvs ++ ) {
 
-			if ( uvs !== undefined ) {
+						uv.x = uvs.getX( i );
+						uv.y = uvs.getY( i ); // transform the uv to export format
 
-				for ( i = 0, l = uvs.count; i < l; i ++, nbVertexUvs ++ ) {
+						output += 'vt ' + uv.x + ' ' + uv.y + '\n';
 
-					uv.x = uvs.getX( i );
-					uv.y = uvs.getY( i );
+					}
 
-					// transform the uv to export format
-					output += 'vt ' + uv.x + ' ' + uv.y + '\n';
+				} // normals
 
-				}
 
-			}
+				if ( normals !== undefined ) {
 
-			// normals
+					normalMatrixWorld.getNormalMatrix( mesh.matrixWorld );
 
-			if ( normals !== undefined ) {
+					for ( let i = 0, l = normals.count; i < l; i ++, nbNormals ++ ) {
 
-				normalMatrixWorld.getNormalMatrix( mesh.matrixWorld );
+						normal.x = normals.getX( i );
+						normal.y = normals.getY( i );
+						normal.z = normals.getZ( i ); // transform the normal to world space
 
-				for ( i = 0, l = normals.count; i < l; i ++, nbNormals ++ ) {
+						normal.applyMatrix3( normalMatrixWorld ).normalize(); // transform the normal to export format
 
-					normal.x = normals.getX( i );
-					normal.y = normals.getY( i );
-					normal.z = normals.getZ( i );
+						output += 'vn ' + normal.x + ' ' + normal.y + ' ' + normal.z + '\n';
 
-					// transform the normal to world space
-					normal.applyMatrix3( normalMatrixWorld ).normalize();
+					}
 
-					// transform the normal to export format
-					output += 'vn ' + normal.x + ' ' + normal.y + ' ' + normal.z + '\n';
+				} // faces
 
-				}
 
-			}
+				if ( indices !== null ) {
 
-			// faces
+					for ( let i = 0, l = indices.count; i < l; i += 3 ) {
 
-			if ( indices !== null ) {
+						for ( let m = 0; m < 3; m ++ ) {
 
-				for ( i = 0, l = indices.count; i < l; i += 3 ) {
+							const j = indices.getX( 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 = 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 ( let i = 0, l = vertices.count; i < l; i += 3 ) {
 
-			} else {
+						for ( let m = 0; m < 3; m ++ ) {
 
-				for ( i = 0, l = vertices.count; i < l; i += 3 ) {
+							const 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;
+			}
 
-		};
+			function parseLine( line ) {
 
-		var parseLine = function ( line ) {
+				let nbVertex = 0;
+				const geometry = line.geometry;
+				const 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.' );
 
-			}
+				const 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 ( let 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' ) {
-
-				output += 'l ';
+				if ( type === 'Line' ) {
 
-				for ( j = 1, l = vertices.count; j <= l; j ++ ) {
+					output += 'l ';
 
-					output += ( indexVertex + j ) + ' ';
+					for ( let 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 ( let 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;
+			function parsePoints( points ) {
 
-			if ( geometry.isBufferGeometry !== true ) {
+				let nbVertex = 0;
+				const 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';
+				const vertices = geometry.getAttribute( 'position' );
+				const 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 ( let 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 ( let 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;
+
+} )();

+ 267 - 351
examples/js/exporters/PLYExporter.js

@@ -1,8 +1,10 @@
-/**
+( function () {
+
+	/**
  * https://github.com/gkjohnson/ply-exporter-js
  *
  * Usage:
- *  var exporter = new THREE.PLYExporter();
+ *  const exporter = new PLYExporter();
  *
  *  // second argument is a list of options
  *  exporter.parse(mesh, data => console.log(data), { binary: true, excludeAttributes: [ 'color' ], littleEndian: true });
@@ -11,513 +13,427 @@
  * http://paulbourke.net/dataformats/ply/
  */
 
-THREE.PLYExporter = function () {};
+	class PLYExporter {
 
-THREE.PLYExporter.prototype = {
+		parse( 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 ) {
+						const mesh = child;
+						const 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
-		};
+			const defaultOptions = {
+				binary: false,
+				excludeAttributes: [],
+				// normal, uv, color, index
+				littleEndian: false
+			};
+			options = Object.assign( defaultOptions, options );
+			const excludeAttributes = options.excludeAttributes;
+			let includeNormals = false;
+			let includeColors = false;
+			let includeUVs = false; // count the vertices, check which properties are used,
+			// and cache the BufferGeometry
 
-		options = Object.assign( defaultOptions, options );
+			let vertexCount = 0;
+			let 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 ) {
+					const mesh = child;
+					const 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.' );
+					const vertices = geometry.getAttribute( 'position' );
+					const normals = geometry.getAttribute( 'normal' );
+					const uvs = geometry.getAttribute( 'uv' );
+					const colors = geometry.getAttribute( 'color' );
+					const indices = geometry.getIndex();
 
-				}
+					if ( vertices === undefined ) {
 
-				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;
+			} );
+			const 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;
-
+			const indexByteCount = 4;
+			let header = 'ply\n' + `format ${options.binary ? options.littleEndian ? 'binary_little_endian' : 'binary_big_endian' : 'ascii'} 1.0\n` + `element vertex ${vertexCount}\n` + // position
+    'property float x\n' + 'property float y\n' + 'property float z\n';
 
-		if ( includeIndices && faceCount !== Math.floor( faceCount ) ) {
+			if ( includeNormals === 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(
+				// normal
+				header += 'property float nx\n' + 'property float ny\n' + 'property float nz\n';
 
-				'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` +
-
-			// position
-			'property float x\n' +
-			'property float y\n' +
-			'property float z\n';
-
-		if ( includeNormals === true ) {
-
-			// 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
 
+			const vertex = new THREE.Vector3();
+			const normalMatrixWorld = new THREE.Matrix3();
+			let 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
+				const headerBin = new TextEncoder().encode( header ); // 3 position values at 4 bytes
+				// 3 normal values at 4 bytes
+				// 3 color channels with 1 byte
+				// 2 uv values at 4 bytes
 
-				normalMatrixWorld.getNormalMatrix( mesh.matrixWorld );
+				const vertexListLength = vertexCount * ( 4 * 3 + ( includeNormals ? 4 * 3 : 0 ) + ( includeColors ? 3 : 0 ) + ( includeUVs ? 4 * 2 : 0 ) ); // 1 byte shape desciptor
+				// 3 vertex indices at ${indexByteCount} bytes
 
-				for ( var i = 0, l = vertices.count; i < l; i ++ ) {
+				const faceListLength = includeIndices ? faceCount * ( indexByteCount * 3 + 1 ) : 0;
+				const output = new DataView( new ArrayBuffer( headerBin.length + vertexListLength + faceListLength ) );
+				new Uint8Array( output.buffer ).set( headerBin, 0 );
+				let vOffset = headerBin.length;
+				let fOffset = headerBin.length + vertexListLength;
+				let writtenVertices = 0;
+				traverseMeshes( function ( mesh, geometry ) {
 
-					vertex.x = vertices.getX( i );
-					vertex.y = vertices.getY( i );
-					vertex.z = vertices.getZ( i );
+					const vertices = geometry.getAttribute( 'position' );
+					const normals = geometry.getAttribute( 'normal' );
+					const uvs = geometry.getAttribute( 'uv' );
+					const colors = geometry.getAttribute( 'color' );
+					const indices = geometry.getIndex();
+					normalMatrixWorld.getNormalMatrix( mesh.matrixWorld );
 
-					vertex.applyMatrix4( mesh.matrixWorld );
+					for ( let i = 0, l = vertices.count; i < l; i ++ ) {
 
+						vertex.x = vertices.getX( i );
+						vertex.y = vertices.getY( i );
+						vertex.z = vertices.getZ( i );
+						vertex.applyMatrix4( mesh.matrixWorld ); // Position information
 
-					// 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 ( let 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 ( let 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 ) {
-
-						for ( var i = 0, l = indices.count; i < l; i += 3 ) {
-
-							output.setUint8( fOffset, 3 );
-							fOffset += 1;
+					} // Save the amount of verts we've already written so we can offset
+					// the face index on the next mesh
 
-							output.setUint32( fOffset, indices.getX( i + 0 ) + writtenVertices, options.littleEndian );
-							fOffset += indexByteCount;
 
-							output.setUint32( fOffset, indices.getX( i + 1 ) + writtenVertices, options.littleEndian );
-							fOffset += indexByteCount;
+					writtenVertices += vertices.count;
 
-							output.setUint32( fOffset, indices.getX( i + 2 ) + writtenVertices, options.littleEndian );
-							fOffset += indexByteCount;
+				} );
+				result = output.buffer;
 
-						}
-
-					} else {
-
-						for ( var i = 0, l = vertices.count; i < l; i += 3 ) {
+			} else {
 
-							output.setUint8( fOffset, 3 );
-							fOffset += 1;
+				// Ascii File Generation
+				// count the number of vertices
+				let writtenVertices = 0;
+				let vertexList = '';
+				let faceList = '';
+				traverseMeshes( function ( mesh, geometry ) {
 
-							output.setUint32( fOffset, writtenVertices + i, options.littleEndian );
-							fOffset += indexByteCount;
+					const vertices = geometry.getAttribute( 'position' );
+					const normals = geometry.getAttribute( 'normal' );
+					const uvs = geometry.getAttribute( 'uv' );
+					const colors = geometry.getAttribute( 'color' );
+					const indices = geometry.getIndex();
+					normalMatrixWorld.getNormalMatrix( mesh.matrixWorld ); // form each line
 
-							output.setUint32( fOffset, writtenVertices + i + 1, options.littleEndian );
-							fOffset += indexByteCount;
+					for ( let i = 0, l = vertices.count; i < l; i ++ ) {
 
-							output.setUint32( fOffset, writtenVertices + i + 2, options.littleEndian );
-							fOffset += indexByteCount;
+						vertex.x = vertices.getX( i );
+						vertex.y = vertices.getY( i );
+						vertex.z = vertices.getZ( i );
+						vertex.applyMatrix4( mesh.matrixWorld ); // Position information
 
-						}
-
-					}
+						let 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';
+							}
 
 						}
 
-					}
+						vertexList += line + '\n';
 
-					// UV information
-					if ( includeUVs === true ) {
+					} // Create the face list
 
-						if ( uvs != null ) {
 
-							line += ' ' +
-								uvs.getX( i ) + ' ' +
-								uvs.getY( i );
+					if ( includeIndices === true ) {
 
-						} else if ( includeUVs !== false ) {
+						if ( indices !== null ) {
 
-							line += ' 0 0';
+							for ( let i = 0, l = indices.count; i < l; i += 3 ) {
 
-						}
+								faceList += `3 ${indices.getX( i + 0 ) + writtenVertices}`;
+								faceList += ` ${indices.getX( i + 1 ) + writtenVertices}`;
+								faceList += ` ${indices.getX( i + 2 ) + writtenVertices}\n`;
 
-					}
-
-					// Color information
-					if ( includeColors === true ) {
-
-						if ( colors != null ) {
-
-							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 ( let 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;
 
-				}
+				} );
+				result = `${header}${vertexList}${includeIndices ? `${faceList}\n` : '\n'}`;
 
-				writtenVertices += vertices.count;
-
-			} );
+			}
 
-			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;
+
+} )();

+ 120 - 126
examples/js/exporters/STLExporter.js

@@ -1,198 +1,190 @@
-/**
+( function () {
+
+	/**
  * Usage:
- *  var exporter = new THREE.STLExporter();
+ *  const exporter = new STLExporter();
  *
  *  // second argument is a list of options
- *  var data = exporter.parse( mesh, { binary: true } );
+ *  const data = exporter.parse( mesh, { binary: true } );
  *
  */
 
-THREE.STLExporter = function () {};
-
-THREE.STLExporter.prototype = {
-
-	constructor: THREE.STLExporter,
+	class STLExporter {
 
-	parse: function ( scene, options ) {
+		parse( scene, options = {} ) {
 
-		if ( options === undefined ) options = {};
+			const binary = options.binary !== undefined ? options.binary : false; //
 
-		var binary = options.binary !== undefined ? options.binary : false;
+			const objects = [];
+			let triangles = 0;
+			scene.traverse( function ( object ) {
 
-		//
+				if ( object.isMesh ) {
 
-		var objects = [];
-		var triangles = 0;
+					const 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.' );
+					const index = geometry.index;
+					const 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' );
-
-				triangles += ( index !== null ) ? ( index.count / 3 ) : ( positionAttribute.count / 3 );
-
-				objects.push( {
-					object3d: object,
-					geometry: geometry
-				} );
-
-			}
-
-		} );
-
-		var output;
-		var offset = 80; // skip header
+			} );
+			let output;
+			let offset = 80; // skip header
 
-		if ( binary === true ) {
-
-			var bufferLength = triangles * 2 + triangles * 3 * 4 * 4 + 80 + 4;
-			var arrayBuffer = new ArrayBuffer( bufferLength );
-			output = new DataView( arrayBuffer );
-			output.setUint32( offset, triangles, true ); offset += 4;
-
-		} else {
-
-			output = '';
-			output += 'solid exported\n';
-
-		}
+			if ( binary === true ) {
 
-		var vA = new THREE.Vector3();
-		var vB = new THREE.Vector3();
-		var vC = new THREE.Vector3();
-		var cb = new THREE.Vector3();
-		var ab = new THREE.Vector3();
-		var normal = new THREE.Vector3();
+				const bufferLength = triangles * 2 + triangles * 3 * 4 * 4 + 80 + 4;
+				const arrayBuffer = new ArrayBuffer( bufferLength );
+				output = new DataView( arrayBuffer );
+				output.setUint32( offset, triangles, true );
+				offset += 4;
 
-		for ( var i = 0, il = objects.length; i < il; i ++ ) {
+			} else {
 
-			var object = objects[ i ].object3d;
-			var geometry = objects[ i ].geometry;
+				output = '';
+				output += 'solid exported\n';
 
-			var index = geometry.index;
-			var positionAttribute = geometry.getAttribute( 'position' );
+			}
 
-			if ( index !== null ) {
+			const vA = new THREE.Vector3();
+			const vB = new THREE.Vector3();
+			const vC = new THREE.Vector3();
+			const cb = new THREE.Vector3();
+			const ab = new THREE.Vector3();
+			const normal = new THREE.Vector3();
 
-				// indexed geometry
+			for ( let i = 0, il = objects.length; i < il; i ++ ) {
 
-				for ( var j = 0; j < index.count; j += 3 ) {
+				const object = objects[ i ].object3d;
+				const geometry = objects[ i ].geometry;
+				const index = geometry.index;
+				const positionAttribute = geometry.getAttribute( 'position' );
 
-					var a = index.getX( j + 0 );
-					var b = index.getX( j + 1 );
-					var c = index.getX( j + 2 );
+				if ( index !== null ) {
 
-					writeFace( a, b, c, positionAttribute, object );
+					// indexed geometry
+					for ( let j = 0; j < index.count; j += 3 ) {
 
-				}
+						const a = index.getX( j + 0 );
+						const b = index.getX( j + 1 );
+						const c = index.getX( j + 2 );
+						writeFace( a, b, c, positionAttribute, object );
 
-			} else {
+					}
 
-				// non-indexed geometry
+				} else {
 
-				for ( var j = 0; j < positionAttribute.count; j += 3 ) {
+					// non-indexed geometry
+					for ( let j = 0; j < positionAttribute.count; j += 3 ) {
 
-					var a = j + 0;
-					var b = j + 1;
-					var c = j + 2;
+						const a = j + 0;
+						const b = j + 1;
+						const c = j + 2;
+						writeFace( a, b, c, positionAttribute, object );
 
-					writeFace( a, b, c, positionAttribute, object );
+					}
 
 				}
 
 			}
 
-		}
-
-		if ( binary === false ) {
+			if ( binary === false ) {
 
-			output += 'endsolid exported\n';
+				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;
+					output += '\tfacet normal ' + normal.x + ' ' + normal.y + ' ' + normal.z + '\n';
+					output += '\t\touter loop\n';
 
-			} else {
-
-				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';
+				}
 
 			}
 
@@ -200,4 +192,6 @@ THREE.STLExporter.prototype = {
 
 	}
 
-};
+	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;
+
+} )();

+ 42 - 45
examples/js/geometries/BoxLineGeometry.js

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

+ 26 - 30
examples/js/geometries/ConvexGeometry.js

@@ -1,52 +1,48 @@
-// ConvexGeometry
+( function () {
 
-THREE.ConvexGeometry = function ( points ) {
+	class ConvexGeometry extends THREE.BufferGeometry {
 
-	THREE.BufferGeometry.call( this );
+		constructor( points ) {
 
-	// buffers
+			super(); // buffers
 
-	var vertices = [];
-	var normals = [];
+			const vertices = [];
+			const 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 );
+			const convexHull = new THREE.ConvexHull().setFromPoints( points ); // generate vertices and normals
 
-	// generate vertices and normals
+			const faces = convexHull.faces;
 
-	var faces = convexHull.faces;
+			for ( let i = 0; i < faces.length; i ++ ) {
 
-	for ( var i = 0; i < faces.length; i ++ ) {
+				const face = faces[ i ];
+				let edge = face.edge; // we move along a doubly-connected edge list to access all face points (see HalfEdge docs)
 
-		var face = faces[ i ];
-		var edge = face.edge;
+				do {
 
-		// we move along a doubly-connected edge list to access all face points (see HalfEdge docs)
+					const 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;
 
-		do {
+				} while ( edge !== face.edge );
 
-			var point = edge.head().point;
+			} // build geometry
 
-			vertices.push( point.x, point.y, point.z );
-			normals.push( face.normal.x, face.normal.y, face.normal.z );
 
-			edge = edge.next;
+			this.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
+			this.setAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) );
 
-		} while ( edge !== face.edge );
+		}
 
 	}
 
-	// build geometry
-
-	this.setAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
-	this.setAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) );
-
-};
+	THREE.ConvexGeometry = ConvexGeometry;
 
-THREE.ConvexGeometry.prototype = Object.create( THREE.BufferGeometry.prototype );
-THREE.ConvexGeometry.prototype.constructor = THREE.ConvexGeometry;
+} )();

+ 187 - 237
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,284 @@
  *
  */
 
-THREE.DecalGeometry = function ( mesh, position, orientation, size ) {
-
-	THREE.BufferGeometry.call( this );
-
-	// buffers
-
-	var vertices = [];
-	var normals = [];
-	var uvs = [];
+	class DecalGeometry extends THREE.BufferGeometry {
 
-	// helpers
+		constructor( mesh, position, orientation, size ) {
 
-	var plane = new THREE.Vector3();
+			super(); // buffers
 
-	// this matrix represents the transformation of the decal projector
+			const vertices = [];
+			const normals = [];
+			const uvs = []; // helpers
 
-	var projectorMatrix = new THREE.Matrix4();
-	projectorMatrix.makeRotationFromEuler( orientation );
-	projectorMatrix.setPosition( position );
+			const plane = new THREE.Vector3(); // this matrix represents the transformation of the decal projector
 
-	var projectorMatrixInverse = new THREE.Matrix4();
-	projectorMatrixInverse.copy( projectorMatrix ).invert();
+			const projectorMatrix = new THREE.Matrix4();
+			projectorMatrix.makeRotationFromEuler( orientation );
+			projectorMatrix.setPosition( position );
+			const 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 ) );
+				let decalVertices = [];
+				const vertex = new THREE.Vector3();
+				const 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
+				const geometry = mesh.geometry;
+				const positionAttribute = geometry.attributes.position;
+				const 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
 
-		if ( mesh.geometry.isGeometry === true ) {
+				if ( geometry.index !== null ) {
 
-			console.error( 'THREE.DecalGeometry no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
-			return;
+					// indexed THREE.BufferGeometry
+					const index = geometry.index;
 
-		}
+					for ( let i = 0; i < index.count; i ++ ) {
 
-		var geometry = mesh.geometry;
+						vertex.fromBufferAttribute( positionAttribute, index.getX( i ) );
+						normal.fromBufferAttribute( normalAttribute, index.getX( i ) );
+						pushDecalVertex( decalVertices, vertex, normal );
 
-		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
+				} else {
 
-		if ( geometry.index !== null ) {
+					// non-indexed THREE.BufferGeometry
+					for ( let i = 0; i < positionAttribute.count; i ++ ) {
 
-			// indexed BufferGeometry
+						vertex.fromBufferAttribute( positionAttribute, i );
+						normal.fromBufferAttribute( normalAttribute, i );
+						pushDecalVertex( decalVertices, vertex, normal );
 
-			var index = geometry.index;
+					}
 
-			for ( i = 0; i < index.count; i ++ ) {
+				} // second, clip the geometry so that it doesn't extend out from the projector
 
-				vertex.fromBufferAttribute( positionAttribute, index.getX( i ) );
-				normal.fromBufferAttribute( normalAttribute, index.getX( i ) );
 
-				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
 
-			}
+				for ( let i = 0; i < decalVertices.length; i ++ ) {
 
-		} else {
+					const decalVertex = decalVertices[ i ]; // create texture coordinates (we are still in projector space)
 
-			// non-indexed BufferGeometry
+					uvs.push( 0.5 + decalVertex.position.x / size.x, 0.5 + decalVertex.position.y / size.y ); // transform the vertex back to world space
 
-			for ( i = 0; i < positionAttribute.count; i ++ ) {
+					decalVertex.position.applyMatrix4( projectorMatrix ); // now create vertex and normal buffer data
 
-				vertex.fromBufferAttribute( positionAttribute, i );
-				normal.fromBufferAttribute( normalAttribute, i );
+					vertices.push( decalVertex.position.x, decalVertex.position.y, decalVertex.position.z );
+					normals.push( decalVertex.normal.x, decalVertex.normal.y, decalVertex.normal.z );
 
-				pushDecalVertex( decalVertices, vertex, normal );
+				}
 
 			}
 
-		}
-
-		// 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
+			function pushDecalVertex( decalVertices, vertex, normal ) {
 
-		for ( i = 0; i < decalVertices.length; i ++ ) {
+				// 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() ) );
 
-			var decalVertex = decalVertices[ i ];
-
-			// create texture coordinates (we are still in projector space)
-
-			uvs.push(
-				0.5 + ( decalVertex.position.x / size.x ),
-				0.5 + ( decalVertex.position.y / size.y )
-			);
+			}
 
-			// transform the vertex back to world space
+			function clipGeometry( inVertices, plane ) {
 
-			decalVertex.position.applyMatrix4( projectorMatrix );
+				const outVertices = [];
+				const s = 0.5 * Math.abs( size.dot( plane ) ); // a single iteration clips one face,
+				// which consists of three consecutive 'DecalVertex' objects
 
-			// now create vertex and normal buffer data
+				for ( let i = 0; i < inVertices.length; i += 3 ) {
 
-			vertices.push( decalVertex.position.x, decalVertex.position.y, decalVertex.position.z );
-			normals.push( decalVertex.normal.x, decalVertex.normal.y, decalVertex.normal.z );
+					let total = 0;
+					let nV1;
+					let nV2;
+					let nV3;
+					let nV4;
+					const d1 = inVertices[ i + 0 ].position.dot( plane ) - s;
+					const d2 = inVertices[ i + 1 ].position.dot( plane ) - s;
+					const d3 = inVertices[ i + 2 ].position.dot( plane ) - s;
+					const v1Out = d1 > 0;
+					const v2Out = d2 > 0;
+					const v3Out = d3 > 0; // calculate, how many vertices of the face lie outside of the clipping plane
 
-		}
+					total = ( v1Out ? 1 : 0 ) + ( v2Out ? 1 : 0 ) + ( v3Out ? 1 : 0 );
 
-	}
+					switch ( total ) {
 
-	function pushDecalVertex( decalVertices, vertex, normal ) {
+						case 0:
+						{
 
-		// transform the vertex to world space, then to projector space
+							// 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;
 
-		vertex.applyMatrix4( mesh.matrixWorld );
-		vertex.applyMatrix4( projectorMatrixInverse );
+						}
 
-		normal.transformDirection( mesh.matrixWorld );
+						case 1:
+						{
 
-		decalVertices.push( new THREE.DecalVertex( vertex.clone(), normal.clone() ) );
+							// one vertex lies outside of the plane, perform clipping
+							if ( v1Out ) {
 
-	}
+								nV1 = inVertices[ i + 1 ];
+								nV2 = inVertices[ i + 2 ];
+								nV3 = clip( inVertices[ i ], nV1, plane, s );
+								nV4 = clip( inVertices[ i ], nV2, plane, s );
 
-	function clipGeometry( inVertices, plane ) {
+							}
 
-		var outVertices = [];
+							if ( v2Out ) {
 
-		var s = 0.5 * Math.abs( size.dot( plane ) );
+								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;
 
-		// a single iteration clips one face,
-		// which consists of three consecutive 'DecalVertex' objects
+							}
 
-		for ( var i = 0; i < inVertices.length; i += 3 ) {
+							if ( v3Out ) {
 
-			var v1Out, v2Out, v3Out, total = 0;
-			var nV1, nV2, nV3, nV4;
+								nV1 = inVertices[ i ];
+								nV2 = inVertices[ i + 1 ];
+								nV3 = clip( inVertices[ i + 2 ], nV1, plane, s );
+								nV4 = clip( inVertices[ i + 2 ], nV2, plane, s );
 
-			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;
+							outVertices.push( nV1.clone() );
+							outVertices.push( nV2.clone() );
+							outVertices.push( nV3 );
+							outVertices.push( nV4 );
+							outVertices.push( nV3.clone() );
+							outVertices.push( nV2.clone() );
+							break;
 
-			// calculate, how many vertices of the face lie outside of the clipping plane
+						}
 
-			total = ( v1Out ? 1 : 0 ) + ( v2Out ? 1 : 0 ) + ( v3Out ? 1 : 0 );
+						case 2:
+						{
 
-			switch ( total ) {
+							// two vertices lies outside of the plane, perform clipping
+							if ( ! v1Out ) {
 
-				case 0: {
+								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 );
 
-					// 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;
+							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 );
 
-				case 1: {
+							}
 
-					// one vertex lies outside of the plane, perform clipping
+							if ( ! v3Out ) {
 
-					if ( v1Out ) {
+								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 );
 
-						nV1 = inVertices[ i + 1 ];
-						nV2 = inVertices[ i + 2 ];
-						nV3 = clip( inVertices[ i ], nV1, plane, s );
-						nV4 = clip( inVertices[ i ], nV2, plane, s );
+							}
 
-					}
+							break;
 
-					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 );
+						case 3:
+						{
 
-						outVertices.push( nV3 );
-						outVertices.push( nV2.clone() );
-						outVertices.push( nV1.clone() );
+							// the entire face lies outside of the plane, so let's discard the corresponding vertices
+							break;
 
-						outVertices.push( nV2.clone() );
-						outVertices.push( nV3.clone() );
-						outVertices.push( nV4 );
-						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 );
-
-					outVertices.push( nV4 );
-					outVertices.push( nV3.clone() );
-					outVertices.push( nV2.clone() );
-
-					break;
-
 				}
 
-				case 2: {
+				return outVertices;
 
-					// two vertices lies outside of the plane, perform clipping
-
-					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 ( ! 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 );
-
-					}
-
-					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 );
-
-					}
-
-					break;
-
-				}
-
-				case 3: {
+			}
 
-					// the entire face lies outside of the plane, so let's discard the corresponding vertices
+			function clip( v0, v1, p, s ) {
 
-					break;
+				const d0 = v0.position.dot( p ) - s;
+				const d1 = v1.position.dot( p ) - s;
+				const s0 = d0 / ( d0 - d1 );
+				const 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 );
 
-				}
+				return v;
 
 			}
 
 		}
 
-		return outVertices;
-
-	}
-
-	function clip( v0, v1, p, s ) {
-
-		var d0 = v0.position.dot( p ) - s;
-		var d1 = v1.position.dot( p ) - s;
+	} // helper
 
-		var s0 = d0 / ( d0 - d1 );
 
-		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 )
-			)
-		);
+	class DecalVertex {
 
-		// need to clip more values (texture coordinates)? do it this way:
-		// intersectpoint.value = a.value + s * ( b.value - a.value );
+		constructor( position, normal ) {
 
-		return v;
+			this.position = position;
+			this.normal = normal;
 
-	}
-
-};
-
-THREE.DecalGeometry.prototype = Object.create( THREE.BufferGeometry.prototype );
-THREE.DecalGeometry.prototype.constructor = THREE.DecalGeometry;
-
-// helper
+		}
 
-THREE.DecalVertex = function ( position, normal ) {
+		clone() {
 
-	this.position = position;
-	this.normal = normal;
+			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;
 
-};
+} )();

+ 560 - 692
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,774 @@
  *
 */
 
-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;
+	class LightningStrike extends THREE.BufferGeometry {
 
-THREE.LightningStrike.COS30DEG = Math.cos( 30 * Math.PI / 180 );
-THREE.LightningStrike.SIN30DEG = Math.sin( 30 * Math.PI / 180 );
+		constructor( rayParameters = {} ) {
 
-THREE.LightningStrike.createRandomGenerator = function () {
+			super();
+			this.type = 'LightningStrike'; // Set parameters, and set undefined parameters to default values
 
-	var numSeeds = 2053;
-	var seeds = [];
+			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 = {
+		static createRandomGenerator() {
 
-		currentSeed: 0,
+			const numSeeds = 2053;
+			const seeds = [];
 
-		random: function () {
+			for ( let i = 0; i < numSeeds; i ++ ) {
 
-			var value = seeds[ generator.currentSeed ];
+				seeds.push( Math.random() );
 
-			generator.currentSeed = ( generator.currentSeed + 1 ) % numSeeds;
+			}
 
-			return value;
+			const generator = {
+				currentSeed: 0,
+				random: function () {
 
-		},
+					const value = seeds[ generator.currentSeed ];
+					generator.currentSeed = ( generator.currentSeed + 1 ) % numSeeds;
+					return value;
 
-		getSeed: function () {
+				},
+				getSeed: function () {
 
-			return generator.currentSeed / numSeeds;
+					return generator.currentSeed / numSeeds;
 
-		},
+				},
+				setSeed: function ( seed ) {
 
-		setSeed: function ( seed ) {
+					generator.currentSeed = Math.floor( seed * numSeeds ) % numSeeds;
 
-			generator.currentSeed = Math.floor( seed * numSeeds ) % numSeeds;
+				}
+			};
+			return generator;
 
 		}
 
-	};
+		static copyParameters( dest = {}, source = {} ) {
 
-	return generator;
+			const vecCopy = function ( v ) {
 
-};
+				if ( source === dest ) {
 
-THREE.LightningStrike.copyParameters = function ( dest, source ) {
+					return v;
 
-	source = source || {};
-	dest = dest || {};
+				} else {
 
-	var vecCopy = function ( v ) {
+					return v.clone();
 
-		if ( source === dest ) {
+				}
 
-			return v;
+			};
 
-		} else {
+			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:
 
-			return v.clone();
+			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;
 
 		}
 
-	};
-
-	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;
+		update( time ) {
 
-	// These parameters cannot change after lightning creation:
+			if ( this.isStatic ) return;
 
-	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;
+			if ( this.rayParameters.isEternal || this.rayParameters.birthTime <= time && time <= this.rayParameters.deathTime ) {
 
-	return dest;
+				this.updateMesh( time );
 
-};
+				if ( time < this.subrays[ 0 ].endPropagationTime ) {
 
-THREE.LightningStrike.prototype.update = function ( time ) {
+					this.state = 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 = LightningStrike.RAY_VANISHING;
 
-		this.updateMesh( time );
+				} else {
 
-		if ( time < this.subrays[ 0 ].endPropagationTime ) {
-
-			this.state = THREE.LightningStrike.RAY_PROPAGATING;
-
-		} else if ( time > this.subrays[ 0 ].beginVanishingTime ) {
-
-			this.state = THREE.LightningStrike.RAY_VANISHING;
-
-		} else {
-
-			this.state = THREE.LightningStrike.RAY_STEADY;
-
-		}
-
-		this.visible = true;
-
-	} else {
-
-		this.visible = false;
-
-		if ( time < this.rayParameters.birthTime ) {
-
-			this.state = THREE.LightningStrike.RAY_UNBORN;
-
-		} else {
-
-			this.state = THREE.LightningStrike.RAY_EXTINGUISHED;
-
-		}
-
-	}
+					this.state = LightningStrike.RAY_STEADY;
 
-};
+				}
 
-THREE.LightningStrike.prototype.init = function ( rayParameters ) {
+				this.visible = true;
 
-	// Init all the state from the parameters
+			} else {
 
-	this.rayParameters = rayParameters;
+				this.visible = false;
 
-	// These parameters cannot change after lightning creation:
+				if ( time < this.rayParameters.birthTime ) {
 
-	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;
+					this.state = LightningStrike.RAY_UNBORN;
 
-	// Random generator
-	if ( rayParameters.randomGenerator !== undefined ) {
+				} else {
 
-		this.randomGenerator = rayParameters.randomGenerator;
-		this.seedGenerator = rayParameters.randomGenerator;
+					this.state = LightningStrike.RAY_EXTINGUISHED;
 
-		if ( rayParameters.noiseSeed !== undefined ) {
+				}
 
-			this.seedGenerator.setSeed( rayParameters.noiseSeed );
+			}
 
 		}
 
-	} else {
+		init( rayParameters ) {
 
-		this.randomGenerator = THREE.LightningStrike.createRandomGenerator();
-		this.seedGenerator = Math;
+			// Init all the state from the parameters
+			this.rayParameters = rayParameters; // These parameters cannot change after lightning creation:
 
-	}
+			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
 
-	// Ray creation callbacks
-	if ( rayParameters.onDecideSubrayCreation !== undefined ) {
+			if ( rayParameters.randomGenerator !== undefined ) {
 
-		this.onDecideSubrayCreation = rayParameters.onDecideSubrayCreation;
+				this.randomGenerator = rayParameters.randomGenerator;
+				this.seedGenerator = rayParameters.randomGenerator;
 
-	} else {
+				if ( rayParameters.noiseSeed !== undefined ) {
 
-		this.createDefaultSubrayCreationCallbacks();
+					this.seedGenerator.setSeed( rayParameters.noiseSeed );
 
-		if ( rayParameters.onSubrayCreation !== undefined ) {
+				}
 
-			this.onSubrayCreation = rayParameters.onSubrayCreation;
+			} else {
 
-		}
-
-	}
+				this.randomGenerator = LightningStrike.createRandomGenerator();
+				this.seedGenerator = Math;
 
-	// Internal state
+			} // Ray creation callbacks
 
-	this.state = THREE.LightningStrike.RAY_INITIALIZED;
 
-	this.maxSubrays = Math.ceil( 1 + Math.pow( this.ramification, Math.max( 0, this.maxSubrayRecursion - 1 ) ) );
-	rayParameters.maxSubrays = this.maxSubrays;
+			if ( rayParameters.onDecideSubrayCreation !== undefined ) {
 
-	this.maxRaySegments = 2 * ( 1 << this.maxIterations );
+				this.onDecideSubrayCreation = rayParameters.onDecideSubrayCreation;
 
-	this.subrays = [];
-
-	for ( var i = 0; i < this.maxSubrays; i ++ ) {
-
-		this.subrays.push( this.createSubray() );
-
-	}
+			} else {
 
-	this.raySegments = [];
+				this.createDefaultSubrayCreationCallbacks();
 
-	for ( var i = 0; i < this.maxRaySegments; i ++ ) {
+				if ( rayParameters.onSubrayCreation !== undefined ) {
 
-		this.raySegments.push( this.createSegment() );
+					this.onSubrayCreation = rayParameters.onSubrayCreation;
 
-	}
+				}
 
-	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();
-
-};
-
-THREE.LightningStrike.prototype.createMesh = function () {
-
-	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 );
-	if ( this.generateUVs ) {
-
-		this.uvs = new Float32Array( maxVerts * 2 );
+			} // Internal state
 
-	}
 
-	// Populate the mesh
-	this.fillMesh( 0 );
+			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.setIndex( new THREE.Uint32BufferAttribute( this.indices, 1 ) );
+			for ( let i = 0; i < this.maxSubrays; i ++ ) {
 
-	this.positionAttribute = new THREE.Float32BufferAttribute( this.vertices, 3 );
-	this.setAttribute( 'position', this.positionAttribute );
+				this.subrays.push( this.createSubray() );
 
-	if ( this.generateUVs ) {
+			}
 
-		this.uvsAttribute = new THREE.Float32BufferAttribute( new Float32Array( this.uvs ), 2 );
-		this.setAttribute( 'uv', this.uvsAttribute );
+			this.raySegments = [];
 
-	}
+			for ( let i = 0; i < this.maxRaySegments; i ++ ) {
 
-	if ( ! this.isStatic ) {
+				this.raySegments.push( this.createSegment() );
 
-		this.index.usage = THREE.DynamicDrawUsage;
-		this.positionAttribute.usage = THREE.DynamicDrawUsage;
-		if ( this.generateUVs ) {
+			}
 
-			this.uvsAttribute.usage = THREE.DynamicDrawUsage;
+			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();
 
 		}
 
-	}
-
-	// Store buffers for later modification
-	this.vertices = this.positionAttribute.array;
-	this.indices = this.index.array;
-	if ( this.generateUVs ) {
-
-		this.uvs = this.uvsAttribute.array;
-
-	}
-
-};
-
-THREE.LightningStrike.prototype.updateMesh = function ( time ) {
-
-	this.fillMesh( time );
-
-	this.drawRange.count = this.currentIndex;
+		createMesh() {
 
-	this.index.needsUpdate = true;
+			const maxDrawableSegmentsPerSubRay = 1 << this.maxIterations;
+			const maxVerts = 3 * ( maxDrawableSegmentsPerSubRay + 1 ) * this.maxSubrays;
+			const maxIndices = 18 * maxDrawableSegmentsPerSubRay * this.maxSubrays;
+			this.vertices = new Float32Array( maxVerts * 3 );
+			this.indices = new Uint32Array( maxIndices );
 
-	this.positionAttribute.needsUpdate = true;
-
-	if ( this.generateUVs ) {
-
-		this.uvsAttribute.needsUpdate = true;
-
-	}
+			if ( this.generateUVs ) {
 
-};
+				this.uvs = new Float32Array( maxVerts * 2 );
 
-THREE.LightningStrike.prototype.fillMesh = function ( time ) {
+			} // Populate the mesh
 
-	var scope = this;
 
-	this.currentVertex = 0;
-	this.currentIndex = 0;
-	this.currentCoordinate = 0;
-	this.currentUVCoordinate = 0;
+			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.fractalRay( time, function fillVertices( segment ) {
+			if ( this.generateUVs ) {
 
-		var subray = scope.currentSubray;
+				this.uvsAttribute = new THREE.Float32BufferAttribute( new Float32Array( this.uvs ), 2 );
+				this.setAttribute( 'uv', this.uvsAttribute );
 
-		if ( time < subray.birthTime ) { //&& ( ! this.rayParameters.isEternal || scope.currentSubray.recursion > 0 ) ) {
+			}
 
-			return;
+			if ( ! this.isStatic ) {
 
-		} else if ( this.rayParameters.isEternal && scope.currentSubray.recursion == 0 ) {
+				this.index.usage = THREE.DynamicDrawUsage;
+				this.positionAttribute.usage = THREE.DynamicDrawUsage;
 
-			// Eternal rays don't propagate nor vanish, but its subrays do
+				if ( this.generateUVs ) {
 
-			scope.createPrism( segment );
+					this.uvsAttribute.usage = THREE.DynamicDrawUsage;
 
-			scope.onDecideSubrayCreation( segment, scope );
+				}
 
-		} else if ( time < subray.endPropagationTime ) {
+			} // Store buffers for later modification
 
-			if ( scope.timeFraction >= segment.fraction0 * subray.propagationTimeFactor ) {
 
-				// Ray propagation has arrived to this segment
+			this.vertices = this.positionAttribute.array;
+			this.indices = this.index.array;
 
-				scope.createPrism( segment );
+			if ( this.generateUVs ) {
 
-				scope.onDecideSubrayCreation( segment, scope );
+				this.uvs = this.uvsAttribute.array;
 
 			}
 
-		} else if ( time < subray.beginVanishingTime ) {
-
-			// Ray is steady (nor propagating nor vanishing)
-
-			scope.createPrism( segment );
-
-			scope.onDecideSubrayCreation( segment, scope );
+		}
 
-		} else {
+		updateMesh( time ) {
 
-			if ( scope.timeFraction <= subray.vanishingTimeFactor + segment.fraction1 * ( 1 - subray.vanishingTimeFactor ) ) {
+			this.fillMesh( time );
+			this.drawRange.count = this.currentIndex;
+			this.index.needsUpdate = true;
+			this.positionAttribute.needsUpdate = true;
 
-				// Segment has not yet vanished
+			if ( this.generateUVs ) {
 
-				scope.createPrism( segment );
+				this.uvsAttribute.needsUpdate = true;
 
 			}
 
-			scope.onDecideSubrayCreation( segment, scope );
-
 		}
 
-	} );
-
-};
+		fillMesh( time ) {
 
-THREE.LightningStrike.prototype.addNewSubray = function ( /*rayParameters*/ ) {
+			const scope = this;
+			this.currentVertex = 0;
+			this.currentIndex = 0;
+			this.currentCoordinate = 0;
+			this.currentUVCoordinate = 0;
+			this.fractalRay( time, function fillVertices( segment ) {
 
-	return this.subrays[ this.numSubrays ++ ];
+				const subray = scope.currentSubray;
 
-};
+				if ( time < subray.birthTime ) {
 
-THREE.LightningStrike.prototype.initSubray = function ( subray, rayParameters ) {
+					//&& ( ! this.rayParameters.isEternal || scope.currentSubray.recursion > 0 ) ) {
+					return;
 
-	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;
+				} else if ( this.rayParameters.isEternal && scope.currentSubray.recursion == 0 ) {
 
-	subray.maxIterations = this.maxIterations;
-	subray.seed = rayParameters.noiseSeed !== undefined ? rayParameters.noiseSeed : 0;
-	subray.recursion = 0;
+					// Eternal rays don't propagate nor vanish, but its subrays do
+					scope.createPrism( segment );
+					scope.onDecideSubrayCreation( segment, scope );
 
-};
+				} else if ( time < subray.endPropagationTime ) {
 
-THREE.LightningStrike.prototype.fractalRay = function ( time, segmentCallback ) {
+					if ( scope.timeFraction >= segment.fraction0 * subray.propagationTimeFactor ) {
 
-	this.time = time;
-	this.currentSegmentCallback = segmentCallback;
-	this.numSubrays = 0;
+						// Ray propagation has arrived to this segment
+						scope.createPrism( segment );
+						scope.onDecideSubrayCreation( segment, scope );
 
-	// 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 ++ ) {
+				} else if ( time < subray.beginVanishingTime ) {
 
-		var subray = this.subrays[ subrayIndex ];
-		this.currentSubray = subray;
+					// Ray is steady (nor propagating nor vanishing)
+					scope.createPrism( segment );
+					scope.onDecideSubrayCreation( segment, scope );
 
-		this.randomGenerator.setSeed( subray.seed );
+				} else {
 
-		subray.endPropagationTime = THREE.MathUtils.lerp( subray.birthTime, subray.deathTime, subray.propagationTimeFactor );
-		subray.beginVanishingTime = THREE.MathUtils.lerp( subray.deathTime, subray.birthTime, 1 - subray.vanishingTimeFactor );
+					if ( scope.timeFraction <= subray.vanishingTimeFactor + segment.fraction1 * ( 1 - subray.vanishingTimeFactor ) ) {
 
-		var random1 = this.randomGenerator.random;
-		subray.linPos0.set( random1(), random1(), random1() ).multiplyScalar( 1000 );
-		subray.linPos1.set( random1(), random1(), random1() ).multiplyScalar( 1000 );
+						// Segment has not yet vanished
+						scope.createPrism( segment );
 
-		this.timeFraction = ( time - subray.birthTime ) / ( subray.deathTime - subray.birthTime );
+					}
 
-		this.currentSegmentIndex = 0;
-		this.isInitialSegment = true;
+					scope.onDecideSubrayCreation( segment, scope );
 
-		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;
+		addNewSubray( ) {
 
-	this.fractalRayRecursive( newSegment1 );
+			return this.subrays[ this.numSubrays ++ ];
 
-	this.fractalRayRecursive( newSegment2 );
+		}
 
-};
+		initSubray( 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.createPrism = function ( segment ) {
+		}
 
-	// Creates one triangular prism and its vertices at the segment
+		fractalRay( 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 ( let subrayIndex = 0; subrayIndex < this.numSubrays; subrayIndex ++ ) {
+
+				const 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 );
+				const 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;
+				const 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.forwardsFill.subVectors( segment.pos1, segment.pos0 ).normalize();
+			}
 
-	if ( this.isInitialSegment ) {
+			this.currentSegmentCallback = null;
+			this.currentSubray = null;
 
-		this.currentCreateTriangleVertices( segment.pos0, segment.up0, this.forwardsFill, segment.radius0, 0 );
+		}
 
-		this.isInitialSegment = false;
+		fractalRayRecursive( segment ) {
 
-	}
+			// Leave recursion condition
+			if ( segment.iteration >= this.currentSubray.maxIterations ) {
 
-	this.currentCreateTriangleVertices( segment.pos1, segment.up0, this.forwardsFill, segment.radius1, segment.fraction1 );
+				this.currentSegmentCallback( segment );
+				return;
 
-	this.createPrismFaces();
+			} // Interpolation
 
-};
 
-THREE.LightningStrike.prototype.createTriangleVerticesWithoutUVs = function ( pos, up, forwards, radius ) {
+			this.forwards.subVectors( segment.pos1, segment.pos0 );
+			let lForwards = this.forwards.length();
 
-	// Create an equilateral triangle (only vertices)
+			if ( lForwards < 0.000001 ) {
 
-	this.side.crossVectors( up, forwards ).multiplyScalar( radius * THREE.LightningStrike.COS30DEG );
-	this.down.copy( up ).multiplyScalar( - radius * THREE.LightningStrike.SIN30DEG );
+				this.forwards.set( 0, 0, 0.01 );
+				lForwards = this.forwards.length();
 
-	var p = this.vPos;
-	var v = this.vertices;
+			}
 
-	p.copy( pos ).sub( this.side ).add( this.down );
+			const middleRadius = ( segment.radius0 + segment.radius1 ) * 0.5;
+			const middleFraction = ( segment.fraction0 + segment.fraction1 ) * 0.5;
+			const 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 );
+			const 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
+
+			const 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;
+			const 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;
+		}
 
-	p.copy( pos ).add( this.side ).add( this.down );
+		createPrism( segment ) {
 
-	v[ this.currentCoordinate ++ ] = p.x;
-	v[ this.currentCoordinate ++ ] = p.y;
-	v[ this.currentCoordinate ++ ] = p.z;
+			// Creates one triangular prism and its vertices at the segment
+			this.forwardsFill.subVectors( segment.pos1, segment.pos0 ).normalize();
 
-	p.copy( up ).multiplyScalar( radius ).add( pos );
+			if ( this.isInitialSegment ) {
 
-	v[ this.currentCoordinate ++ ] = p.x;
-	v[ this.currentCoordinate ++ ] = p.y;
-	v[ this.currentCoordinate ++ ] = p.z;
+				this.currentCreateTriangleVertices( segment.pos0, segment.up0, this.forwardsFill, segment.radius0, 0 );
+				this.isInitialSegment = false;
 
-	this.currentVertex += 3;
+			}
 
-};
+			this.currentCreateTriangleVertices( segment.pos1, segment.up0, this.forwardsFill, segment.radius1, segment.fraction1 );
+			this.createPrismFaces();
 
-THREE.LightningStrike.prototype.createTriangleVerticesWithUVs = function ( pos, up, forwards, radius, u ) {
+		}
 
-	// Create an equilateral triangle (only vertices)
+		createTriangleVerticesWithoutUVs( 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 );
+			const p = this.vPos;
+			const 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;
 
-	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;
-	var uv = this.uvs;
+		createTriangleVerticesWithUVs( 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 );
+			const p = this.vPos;
+			const v = this.vertices;
+			const 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;
 
-	p.copy( pos ).sub( this.side ).add( this.down );
+		}
 
-	v[ this.currentCoordinate ++ ] = p.x;
-	v[ this.currentCoordinate ++ ] = p.y;
-	v[ this.currentCoordinate ++ ] = p.z;
+		createPrismFaces( vertex
+			/*, index*/
+		) {
+
+			const indices = this.indices;
+			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;
 
-	uv[ this.currentUVCoordinate ++ ] = u;
-	uv[ this.currentUVCoordinate ++ ] = 0;
+		}
 
-	p.copy( pos ).add( this.side ).add( this.down );
+		createDefaultSubrayCreationCallbacks() {
 
-	v[ this.currentCoordinate ++ ] = p.x;
-	v[ this.currentCoordinate ++ ] = p.y;
-	v[ this.currentCoordinate ++ ] = p.z;
+			const random1 = this.randomGenerator.random;
 
-	uv[ this.currentUVCoordinate ++ ] = u;
-	uv[ this.currentUVCoordinate ++ ] = 0.5;
+			this.onDecideSubrayCreation = function ( segment, lightningStrike ) {
 
-	p.copy( up ).multiplyScalar( radius ).add( pos );
+				// Decide subrays creation at parent (sub)ray segment
+				const subray = lightningStrike.currentSubray;
+				const period = lightningStrike.rayParameters.subrayPeriod;
+				const dutyCycle = lightningStrike.rayParameters.subrayDutyCycle;
+				const phase0 = lightningStrike.rayParameters.isEternal && subray.recursion == 0 ? - random1() * period : THREE.MathUtils.lerp( subray.birthTime, subray.endPropagationTime, segment.fraction0 ) - random1() * period;
+				const phase = lightningStrike.time - phase0;
+				const currentCycle = Math.floor( phase / period );
+				const childSubraySeed = random1() * ( currentCycle + 1 );
+				const isActive = phase % period <= dutyCycle * period;
+				let probability = 0;
 
-	v[ this.currentCoordinate ++ ] = p.x;
-	v[ this.currentCoordinate ++ ] = p.y;
-	v[ this.currentCoordinate ++ ] = p.z;
+				if ( isActive ) {
 
-	uv[ this.currentUVCoordinate ++ ] = u;
-	uv[ this.currentUVCoordinate ++ ] = 1;
+					probability = lightningStrike.subrayProbability; // Distribution test: probability *= segment.fraction0 > 0.5 && segment.fraction0 < 0.9 ? 1 / 0.4 : 0;
 
-	this.currentVertex += 3;
+				}
 
-};
+				if ( subray.recursion < lightningStrike.maxSubrayRecursion && lightningStrike.numSubrays < lightningStrike.maxSubrays && random1() < probability ) {
 
-THREE.LightningStrike.prototype.createPrismFaces = function ( vertex/*, index*/ ) {
+					const childSubray = lightningStrike.addNewSubray();
+					const 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;
 
-	var indices = this.indices;
-	var vertex = this.currentVertex - 6;
+					if ( ! lightningStrike.rayParameters.isEternal && subray.recursion == 0 ) {
 
-	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;
+						childSubray.birthTime = Math.max( childSubray.birthTime, subray.birthTime );
+						childSubray.deathTime = Math.min( childSubray.deathTime, subray.deathTime );
 
-};
+					}
 
-THREE.LightningStrike.prototype.createDefaultSubrayCreationCallbacks = function () {
+					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 );
 
-	var random1 = this.randomGenerator.random;
+				}
 
-	this.onDecideSubrayCreation = function ( segment, lightningStrike ) {
+			};
 
-		// Decide subrays creation at parent (sub)ray segment
+			const vec1Pos = new THREE.Vector3();
+			const vec2Forward = new THREE.Vector3();
+			const vec3Side = new THREE.Vector3();
+			const vec4Up = new THREE.Vector3();
 
-		var subray = lightningStrike.currentSubray;
+			this.onSubrayCreation = function ( segment, parentSubray, childSubray, lightningStrike ) {
 
-		var period = lightningStrike.rayParameters.subrayPeriod;
-		var dutyCycle = lightningStrike.rayParameters.subrayDutyCycle;
+				// 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 );
 
-		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 );
+			this.subrayConePosition = function ( segment, parentSubray, childSubray, heightFactor, sideWidthFactor, minSideWidthFactor ) {
 
-		var childSubraySeed = random1() * ( currentCycle + 1 );
+				// 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 ) );
+				const length = vec1Pos.length();
+				vec3Side.crossVectors( parentSubray.up0, vec2Forward );
+				const 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 );
 
-		var isActive = phase % period <= dutyCycle * period;
+			};
 
-		var probability = 0;
+			this.subrayCylinderPosition = function ( segment, parentSubray, childSubray, heightFactor, sideWidthFactor, minSideWidthFactor ) {
 
-		if ( isActive ) {
+				// 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 ) );
+				const length = vec1Pos.length();
+				vec3Side.crossVectors( parentSubray.up0, vec2Forward );
+				const 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 );
 
-			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 );
+		createSubray() {
+
+			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
+			};
 
 		}
 
-	};
-
-	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 );
-
-	};
-
-	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 ) );
+		createSegment() {
+
+			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
+			};
 
-		childSubray.pos1.copy( vec3Side ).add( vec4Up ).multiplyScalar( length * sideWidthFactor * ( minSideWidthFactor + random1() * ( 1 - minSideWidthFactor ) ) ).add( vec1Pos ).add( parentSubray.pos0 );
-
-	};
-
-	this.subrayCylinderPosition = function ( segment, parentSubray, childSubray, heightFactor, sideWidthFactor, minSideWidthFactor ) {
-
-		// 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 );
-
-	};
-
-};
-
-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
-
-	};
-
-};
+		}
 
-THREE.LightningStrike.prototype.createSegment = function () {
+		getNewSegment() {
 
-	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
-	};
+			return this.raySegments[ this.currentSegmentIndex ++ ];
 
-};
+		}
 
-THREE.LightningStrike.prototype.getNewSegment = function () {
+		copy( source ) {
 
-	return this.raySegments[ this.currentSegmentIndex ++ ];
+			super.copy( source );
+			this.init( LightningStrike.copyParameters( {}, source.rayParameters ) );
+			return this;
 
-};
+		}
 
-THREE.LightningStrike.prototype.copy = function ( source ) {
+		clone() {
 
-	THREE.BufferGeometry.prototype.copy.call( this, source );
+			return new this.constructor( LightningStrike.copyParameters( {}, this.rayParameters ) );
 
-	this.init( THREE.LightningStrike.copyParameters( {}, source.rayParameters ) );
+		}
 
-	return this;
+	}
 
-};
+	LightningStrike.prototype.isLightningStrike = true; // Ray states
 
-THREE.LightningStrike.prototype.clone = 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 new this.constructor( THREE.LightningStrike.copyParameters( {}, this.rayParameters ) );
+	THREE.LightningStrike = LightningStrike;
 
-};
+} )();

+ 141 - 178
examples/js/geometries/ParametricGeometries.js

@@ -1,252 +1,215 @@
-/**
+( 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 );
+	const 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;
+			let x, 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;
+			}
 
+			const 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;
-
-		var a = 2;
+		},
+		plane: function ( width, height ) {
 
-		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 );
+			return function ( u, v, target ) {
 
-		target.set( x, y, z );
+				const x = u * width;
+				const y = 0;
+				const z = v * height;
+				target.set( x, y, z );
 
-	},
+			};
 
-	mobius3d: function ( u, t, target ) {
+		},
+		mobius: 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;
+			const v = 2 * Math.PI * t;
+			const a = 2;
+			const x = Math.cos( v ) * ( a + u * Math.cos( v / 2 ) );
+			const y = Math.sin( v ) * ( a + u * Math.cos( v / 2 ) );
+			const 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;
+			const phi = u / 2;
+			const major = 2.25,
+				a = 0.125,
+				b = 0.65;
+			let x = a * Math.cos( t ) * Math.cos( phi ) - b * Math.sin( t ) * Math.sin( phi );
+			const z = a * Math.cos( t ) * Math.sin( phi ) + b * Math.sin( t ) * Math.cos( phi );
+			const 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;
+	ParametricGeometries.TubeGeometry = class TubeGeometry extends THREE.ParametricGeometry {
 
-	var frames = path.computeFrenetFrames( segments, closed ),
-		tangents = frames.tangents,
-		normals = frames.normals,
-		binormals = frames.binormals;
+		constructor( path, segments = 64, radius = 1, segmentsRadius = 8, closed = false ) {
 
-	// proxy internals
+			const numpoints = segments + 1;
+			const frames = path.computeFrenetFrames( segments, closed ),
+				tangents = frames.tangents,
+				normals = frames.normals,
+				binormals = frames.binormals;
+			const position = new THREE.Vector3();
 
-	this.tangents = tangents;
-	this.normals = normals;
-	this.binormals = binormals;
+			function ParametricTube( u, v, target ) {
 
-	var position = new THREE.Vector3();
+				v *= 2 * Math.PI;
+				const i = Math.floor( u * ( numpoints - 1 ) );
+				path.getPointAt( u, position );
+				const normal = normals[ i ];
+				const binormal = binormals[ i ];
+				const cx = - radius * Math.cos( v ); // TODO: Hack: Negating it so it faces outside.
 
-	var ParametricTube = function ( u, v, target ) {
+				const cy = 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 );
 
-		v *= 2 * Math.PI;
+			}
 
-		var i = u * ( numpoints - 1 );
-		i = Math.floor( i );
+			super( ParametricTube, segments, segmentsRadius ); // proxy internals
 
-		path.getPointAt( u, position );
+			this.tangents = tangents;
+			this.normals = normals;
+			this.binormals = binormals;
+			this.path = path;
+			this.segments = segments;
+			this.radius = radius;
+			this.segmentsRadius = segmentsRadius;
+			this.closed = closed;
 
-		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.ParametricGeometries.TubeGeometry.prototype = Object.create( THREE.BufferGeometry.prototype );
-THREE.ParametricGeometries.TubeGeometry.prototype.constructor = THREE.ParametricGeometries.TubeGeometry;
-
-
-/*********************************************
+	/*********************************************
   *
   * Parametric Replacement for TorusKnotGeometry
   *
   *********************************************/
-THREE.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;
 
-	function TorusKnotCurve() {
+	ParametricGeometries.TorusKnotGeometry = class TorusKnotGeometry extends ParametricGeometries.TubeGeometry {
 
-		THREE.Curve.call( this );
+		constructor( radius = 200, tube = 40, segmentsT = 64, segmentsR = 8, p = 2, q = 3 ) {
 
-	}
+			class TorusKnotCurve extends THREE.Curve {
 
-	TorusKnotCurve.prototype = Object.create( THREE.Curve.prototype );
-	TorusKnotCurve.prototype.constructor = TorusKnotCurve;
+				getPoint( t, optionalTarget = new THREE.Vector3() ) {
 
-	TorusKnotCurve.prototype.getPoint = function ( t, optionalTarget ) {
+					const point = optionalTarget;
+					t *= Math.PI * 2;
+					const r = 0.5;
+					const x = ( 1 + r * Math.cos( q * t ) ) * Math.cos( p * t );
+					const y = ( 1 + r * Math.cos( q * t ) ) * Math.sin( p * t );
+					const z = r * Math.sin( q * t );
+					return point.set( x, y, z ).multiplyScalar( radius );
 
-		var point = optionalTarget || new THREE.Vector3();
+				}
 
-		t *= Math.PI * 2;
+			}
 
-		var r = 0.5;
+			const segments = segmentsT;
+			const radiusSegments = segmentsR;
+			const extrudePath = new TorusKnotCurve();
+			super( extrudePath, segments, tube, radiusSegments, true, false );
+			this.radius = radius;
+			this.tube = tube;
+			this.segmentsT = segmentsT;
+			this.segmentsR = segmentsR;
+			this.p = p;
+			this.q = q;
 
-		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();
-
-	THREE.ParametricGeometries.TubeGeometry.call( this, extrudePath, segments, tube, radiusSegments, true, false );
-
-};
-
-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 );
+	ParametricGeometries.SphereGeometry = class SphereGeometry extends THREE.ParametricGeometry {
 
-		target.set( x, y, z );
+		constructor( size, u, v ) {
 
-	}
+			function sphere( u, v, target ) {
 
-	THREE.ParametricGeometry.call( this, sphere, u, v );
+				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.ParametricGeometries.SphereGeometry.prototype = Object.create( THREE.BufferGeometry.prototype );
-THREE.ParametricGeometries.SphereGeometry.prototype.constructor = THREE.ParametricGeometries.SphereGeometry;
+			super( sphere, u, v );
 
+		}
 
-/*********************************************
+	};
+	/*********************************************
   *
   * Parametric Replacement for PlaneGeometry
   *
   *********************************************/
 
-THREE.ParametricGeometries.PlaneGeometry = function ( width, depth, segmentsWidth, segmentsDepth ) {
+	ParametricGeometries.PlaneGeometry = class PlaneGeometry extends THREE.ParametricGeometry {
+
+		constructor( 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;
+				const x = u * width;
+				const y = 0;
+				const z = v * depth;
+				target.set( x, y, z );
 
-		target.set( x, y, z );
+			}
 
-	}
+			super( plane, segmentsWidth, segmentsDepth );
 
-	THREE.ParametricGeometry.call( this, plane, segmentsWidth, segmentsDepth );
+		}
+
+	};
 
-};
+	THREE.ParametricGeometries = ParametricGeometries;
 
-THREE.ParametricGeometries.PlaneGeometry.prototype = Object.create( THREE.BufferGeometry.prototype );
-THREE.ParametricGeometries.PlaneGeometry.prototype.constructor = THREE.ParametricGeometries.PlaneGeometry;
+} )();

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

@@ -0,0 +1,140 @@
+( 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;
+
+} )();

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 21 - 454
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;
+
+} )();

+ 291 - 0
examples/js/interactive/HTMLMesh.js

@@ -0,0 +1,291 @@
+( function () {
+
+	class HTMLMesh extends THREE.Mesh {
+
+		constructor( dom ) {
+
+			const texture = new HTMLTexture( dom );
+			const geometry = new THREE.PlaneGeometry( texture.image.width * 0.001, texture.image.height * 0.001 );
+			const material = new THREE.MeshBasicMaterial( {
+				map: texture,
+				toneMapped: false
+			} );
+			super( geometry, material );
+
+			function onEvent( event ) {
+
+				material.map.dispatchEvent( event );
+
+			}
+
+			this.addEventListener( 'mousedown', onEvent );
+			this.addEventListener( 'mousemove', onEvent );
+			this.addEventListener( 'mouseup', onEvent );
+			this.addEventListener( 'click', onEvent );
+
+		}
+
+	}
+
+	class HTMLTexture extends THREE.CanvasTexture {
+
+		constructor( dom ) {
+
+			super( html2canvas( dom ) );
+			this.dom = dom;
+			this.anisotropy = 16;
+			this.encoding = THREE.sRGBEncoding;
+			this.minFilter = THREE.LinearFilter;
+			this.magFilter = THREE.LinearFilter;
+
+		}
+
+		dispatchEvent( event ) {
+
+			htmlevent( this.dom, event.type, event.data.x, event.data.y );
+			this.update();
+
+		}
+
+		update() {
+
+			this.image = html2canvas( this.dom );
+			this.needsUpdate = true;
+
+		}
+
+	} //
+
+
+	function html2canvas( element ) {
+
+		var range = document.createRange();
+
+		function Clipper( context ) {
+
+			var clips = [];
+			var isClipping = false;
+
+			function doClip() {
+
+				if ( isClipping ) {
+
+					isClipping = false;
+					context.restore();
+
+				}
+
+				if ( clips.length === 0 ) return;
+				var minX = - Infinity,
+					minY = - Infinity;
+				var maxX = Infinity,
+					maxY = Infinity;
+
+				for ( var i = 0; i < clips.length; i ++ ) {
+
+					var clip = clips[ i ];
+					minX = Math.max( minX, clip.x );
+					minY = Math.max( minY, clip.y );
+					maxX = Math.min( maxX, clip.x + clip.width );
+					maxY = Math.min( maxY, clip.y + clip.height );
+
+				}
+
+				context.save();
+				context.beginPath();
+				context.rect( minX, minY, maxX - minX, maxY - minY );
+				context.clip();
+				isClipping = true;
+
+			}
+
+			return {
+				add: function ( clip ) {
+
+					clips.push( clip );
+					doClip();
+
+				},
+				remove: function () {
+
+					clips.pop();
+					doClip();
+
+				}
+			};
+
+		}
+
+		function drawText( style, x, y, string ) {
+
+			if ( string !== '' ) {
+
+				if ( style.textTransform === 'uppercase' ) {
+
+					string = string.toUpperCase();
+
+				}
+
+				context.font = style.fontSize + ' ' + style.fontFamily;
+				context.textBaseline = 'top';
+				context.fillStyle = style.color;
+				context.fillText( string, x, y );
+
+			}
+
+		}
+
+		function drawBorder( style, which, x, y, width, height ) {
+
+			var borderWidth = style[ which + 'Width' ];
+			var borderStyle = style[ which + 'Style' ];
+			var borderColor = style[ which + 'Color' ];
+
+			if ( borderWidth !== '0px' && borderStyle !== 'none' && borderColor !== 'transparent' && borderColor !== 'rgba(0, 0, 0, 0)' ) {
+
+				context.strokeStyle = borderColor;
+				context.beginPath();
+				context.moveTo( x, y );
+				context.lineTo( x + width, y + height );
+				context.stroke();
+
+			}
+
+		}
+
+		function drawElement( element, style ) {
+
+			var x = 0,
+				y = 0,
+				width = 0,
+				height = 0;
+
+			if ( element.nodeType === 3 ) {
+
+				// text
+				range.selectNode( element );
+				var rect = range.getBoundingClientRect();
+				x = rect.left - offset.left - 0.5;
+				y = rect.top - offset.top - 0.5;
+				width = rect.width;
+				height = rect.height;
+				drawText( style, x, y, element.nodeValue.trim() );
+
+			} else {
+
+				if ( element.style.display === 'none' ) return;
+				var rect = element.getBoundingClientRect();
+				x = rect.left - offset.left - 0.5;
+				y = rect.top - offset.top - 0.5;
+				width = rect.width;
+				height = rect.height;
+				style = window.getComputedStyle( element );
+				var backgroundColor = style.backgroundColor;
+
+				if ( backgroundColor !== 'transparent' && backgroundColor !== 'rgba(0, 0, 0, 0)' ) {
+
+					context.fillStyle = backgroundColor;
+					context.fillRect( x, y, width, height );
+
+				}
+
+				drawBorder( style, 'borderTop', x, y, width, 0 );
+				drawBorder( style, 'borderLeft', x, y, 0, height );
+				drawBorder( style, 'borderBottom', x, y + height, width, 0 );
+				drawBorder( style, 'borderRight', x + width, y, 0, height );
+
+				if ( element.type === 'color' || element.type === 'text' ) {
+
+					clipper.add( {
+						x: x,
+						y: y,
+						width: width,
+						height: height
+					} );
+					drawText( style, x + parseInt( style.paddingLeft ), y + parseInt( style.paddingTop ), element.value );
+					clipper.remove();
+
+				}
+
+			}
+			/*
+    // debug
+    context.strokeStyle = '#' + Math.random().toString( 16 ).slice( - 3 );
+    context.strokeRect( x - 0.5, y - 0.5, width + 1, height + 1 );
+    */
+
+
+			var isClipping = style.overflow === 'auto' || style.overflow === 'hidden';
+			if ( isClipping ) clipper.add( {
+				x: x,
+				y: y,
+				width: width,
+				height: height
+			} );
+
+			for ( var i = 0; i < element.childNodes.length; i ++ ) {
+
+				drawElement( element.childNodes[ i ], style );
+
+			}
+
+			if ( isClipping ) clipper.remove();
+
+		}
+
+		var offset = element.getBoundingClientRect();
+		var canvas = document.createElement( 'canvas' );
+		canvas.width = offset.width;
+		canvas.height = offset.height;
+		var context = canvas.getContext( '2d'
+			/*, { alpha: false }*/
+		);
+		var clipper = new Clipper( context ); // console.time( 'drawElement' );
+
+		drawElement( element ); // console.timeEnd( 'drawElement' );
+
+		return canvas;
+
+	}
+
+	function htmlevent( element, event, x, y ) {
+
+		const mouseEventInit = {
+			clientX: x * element.offsetWidth + element.offsetLeft,
+			clientY: y * element.offsetHeight + element.offsetTop,
+			view: element.ownerDocument.defaultView
+		};
+		window.dispatchEvent( new MouseEvent( event, mouseEventInit ) );
+		const rect = element.getBoundingClientRect();
+		x = x * rect.width + rect.left;
+		y = y * rect.height + rect.top;
+
+		function traverse( element ) {
+
+			if ( element.nodeType !== 3 ) {
+
+				const rect = element.getBoundingClientRect();
+
+				if ( x > rect.left && x < rect.right && y > rect.top && y < rect.bottom ) {
+
+					element.dispatchEvent( new MouseEvent( event, mouseEventInit ) );
+
+				}
+
+				for ( var i = 0; i < element.childNodes.length; i ++ ) {
+
+					traverse( element.childNodes[ i ] );
+
+				}
+
+			}
+
+		}
+
+		traverse( element );
+
+	}
+
+	THREE.HTMLMesh = HTMLMesh;
+
+} )();

+ 100 - 0
examples/js/interactive/InteractiveGroup.js

@@ -0,0 +1,100 @@
+( function () {
+
+	const _pointer = new THREE.Vector2();
+
+	const _event = {
+		type: '',
+		data: _pointer
+	};
+
+	class InteractiveGroup extends THREE.Group {
+
+		constructor( renderer, camera ) {
+
+			super();
+			const scope = this;
+			const raycaster = new THREE.Raycaster();
+			const tempMatrix = new THREE.Matrix4(); // Pointer Events
+
+			const element = renderer.domElement;
+
+			function onPointerEvent( event ) {
+
+				event.stopPropagation();
+				_pointer.x = event.clientX / element.clientWidth * 2 - 1;
+				_pointer.y = - ( event.clientY / element.clientHeight ) * 2 + 1;
+				raycaster.setFromCamera( _pointer, camera );
+				const intersects = raycaster.intersectObjects( scope.children );
+
+				if ( intersects.length > 0 ) {
+
+					const intersection = intersects[ 0 ];
+					const object = intersection.object;
+					const uv = intersection.uv;
+					_event.type = event.type;
+
+					_event.data.set( uv.x, 1 - uv.y );
+
+					object.dispatchEvent( _event );
+
+				}
+
+			}
+
+			element.addEventListener( 'pointerdown', onPointerEvent );
+			element.addEventListener( 'pointerup', onPointerEvent );
+			element.addEventListener( 'pointermove', onPointerEvent );
+			element.addEventListener( 'mousedown', onPointerEvent );
+			element.addEventListener( 'mouseup', onPointerEvent );
+			element.addEventListener( 'mousemove', onPointerEvent );
+			element.addEventListener( 'click', onPointerEvent ); // WebXR Controller Events
+			// TODO: Dispatch pointerevents too
+
+			const events = {
+				'move': 'mousemove',
+				'select': 'click',
+				'selectstart': 'mousedown',
+				'selectend': 'mouseup'
+			};
+
+			function onXRControllerEvent( event ) {
+
+				const controller = event.target;
+				tempMatrix.identity().extractRotation( controller.matrixWorld );
+				raycaster.ray.origin.setFromMatrixPosition( controller.matrixWorld );
+				raycaster.ray.direction.set( 0, 0, - 1 ).applyMatrix4( tempMatrix );
+				const intersections = raycaster.intersectObjects( scope.children );
+
+				if ( intersections.length > 0 ) {
+
+					const intersection = intersections[ 0 ];
+					const object = intersection.object;
+					const uv = intersection.uv;
+					_event.type = events[ event.type ];
+
+					_event.data.set( uv.x, 1 - uv.y );
+
+					object.dispatchEvent( _event );
+
+				}
+
+			}
+
+			const controller1 = renderer.xr.getController( 0 );
+			controller1.addEventListener( 'move', onXRControllerEvent );
+			controller1.addEventListener( 'select', onXRControllerEvent );
+			controller1.addEventListener( 'selectstart', onXRControllerEvent );
+			controller1.addEventListener( 'selectend', onXRControllerEvent );
+			const controller2 = renderer.xr.getController( 1 );
+			controller2.addEventListener( 'move', onXRControllerEvent );
+			controller2.addEventListener( 'select', onXRControllerEvent );
+			controller2.addEventListener( 'selectstart', onXRControllerEvent );
+			controller2.addEventListener( 'selectend', onXRControllerEvent );
+
+		}
+
+	}
+
+	THREE.InteractiveGroup = InteractiveGroup;
+
+} )();

+ 181 - 143
examples/js/interactive/SelectionBox.js

@@ -1,197 +1,235 @@
-/**
+( 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();
+	const _frustum = new THREE.Frustum();
 
-	var tmpPoint = new THREE.Vector3();
+	const _center = 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();
+	const _tmpPoint = new THREE.Vector3();
 
-	var vecFarTopLeft = new THREE.Vector3();
-	var vecFarTopRight = new THREE.Vector3();
-	var vecFarDownRight = new THREE.Vector3();
-	var vecFarDownLeft = new THREE.Vector3();
+	const _vecNear = new THREE.Vector3();
 
-	var vectemp1 = new THREE.Vector3();
-	var vectemp2 = new THREE.Vector3();
-	var vectemp3 = new THREE.Vector3();
+	const _vecTopLeft = new THREE.Vector3();
 
-	function SelectionBox( camera, scene, deep ) {
+	const _vecTopRight = new THREE.Vector3();
 
-		this.camera = camera;
-		this.scene = scene;
-		this.startPoint = new THREE.Vector3();
-		this.endPoint = new THREE.Vector3();
-		this.collection = [];
-		this.deep = deep || Number.MAX_VALUE;
+	const _vecDownRight = new THREE.Vector3();
 
-	}
+	const _vecDownLeft = new THREE.Vector3();
 
-	SelectionBox.prototype.select = function ( startPoint, endPoint ) {
+	const _vecFarTopLeft = new THREE.Vector3();
 
-		this.startPoint = startPoint || this.startPoint;
-		this.endPoint = endPoint || this.endPoint;
-		this.collection = [];
+	const _vecFarTopRight = new THREE.Vector3();
 
-		this.updateFrustum( this.startPoint, this.endPoint );
-		this.searchChildInFrustum( frustum, this.scene );
+	const _vecFarDownRight = new THREE.Vector3();
 
-		return this.collection;
+	const _vecFarDownLeft = new THREE.Vector3();
 
-	};
+	const _vectemp1 = new THREE.Vector3();
 
-	SelectionBox.prototype.updateFrustum = function ( startPoint, endPoint ) {
+	const _vectemp2 = new THREE.Vector3();
 
-		startPoint = startPoint || this.startPoint;
-		endPoint = endPoint || this.endPoint;
+	const _vectemp3 = new THREE.Vector3();
 
-		// Avoid invalid frustum
+	class SelectionBox {
 
-		if ( startPoint.x === endPoint.x ) {
+		constructor( camera, scene, deep = Number.MAX_VALUE ) {
 
-			endPoint.x += Number.EPSILON;
+			this.camera = camera;
+			this.scene = scene;
+			this.startPoint = new THREE.Vector3();
+			this.endPoint = new THREE.Vector3();
+			this.collection = [];
+			this.deep = deep;
 
 		}
 
-		if ( startPoint.y === endPoint.y ) {
+		select( startPoint, endPoint ) {
 
-			endPoint.y += Number.EPSILON;
+			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;
 
 		}
 
-		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.' );
+		updateFrustum( startPoint, endPoint ) {
+
+			startPoint = startPoint || this.startPoint;
+			endPoint = endPoint || this.endPoint; // Avoid invalid frustum
+
+			if ( startPoint.x === endPoint.x ) {
+
+				endPoint.x += 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 );
+
+				const 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 ) {
+
+				const left = Math.min( startPoint.x, endPoint.x );
+				const top = Math.max( startPoint.y, endPoint.y );
+				const right = Math.max( startPoint.x, endPoint.x );
+				const 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 );
+
+				const 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.' );
+
+			}
 
 		}
 
-	};
+		searchChildInFrustum( frustum, object ) {
 
-	SelectionBox.prototype.searchChildInFrustum = function ( frustum, object ) {
+			if ( object.isMesh || object.isLine || object.isPoints ) {
 
-		if ( object.isMesh || object.isLine || object.isPoints ) {
+				if ( object.material !== undefined ) {
 
-			if ( object.material !== undefined ) {
+					if ( object.geometry.boundingSphere === null ) object.geometry.computeBoundingSphere();
 
-				if ( object.geometry.boundingSphere === null ) object.geometry.computeBoundingSphere();
+					_center.copy( object.geometry.boundingSphere.center );
 
-				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 ( let 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;
+	THREE.SelectionBox = SelectionBox;
 
 } )();

+ 49 - 54
examples/js/interactive/SelectionHelper.js

@@ -1,79 +1,74 @@
-THREE.SelectionHelper = ( function () {
+( function () {
 
-	function SelectionHelper( selectionBox, renderer, cssClassName ) {
+	class SelectionHelper {
 
-		this.element = document.createElement( 'div' );
-		this.element.classList.add( cssClassName );
-		this.element.style.pointerEvents = 'none';
+		constructor( 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 );
+		onSelectStart( 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;
+		}
 
-	};
+		onSelectMove( 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';
+		onSelectOver() {
 
-	};
+			this.element.parentElement.removeChild( this.element );
 
-	SelectionHelper.prototype.onSelectOver = function () {
+		}
 
-		this.element.parentElement.removeChild( this.element );
-
-	};
+	}
 
-	return SelectionHelper;
+	THREE.SelectionHelper = SelectionHelper;
 
 } )();

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 0 - 1
examples/js/libs/chevrotain.min.js


Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 1 - 0
examples/js/libs/fflate.min.js


+ 142 - 153
examples/js/lights/LightProbeGenerator.js

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

Файлын зөрүү хэтэрхий том тул дарагдсан байна
+ 6 - 11
examples/js/lights/RectAreaLightUniformsLib.js


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

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

+ 51 - 56
examples/js/lines/LineGeometry.js

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

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

@@ -1,4 +1,6 @@
-/**
+( function () {
+
+	/**
  * parameters = {
  *  color: <hex>,
  *  linewidth: <float>,
@@ -11,28 +13,33 @@
  * }
  */
 
-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,164 @@ 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
-
-	} );
-
-	this.dashed = false;
-
-	Object.defineProperties( this, {
-
-		color: {
-
-			enumerable: true,
-
-			get: function () {
-
-				return this.uniforms.diffuse.value;
-
-			},
-
-			set: function ( value ) {
-
-				this.uniforms.diffuse.value = value;
-
-			}
-
-		},
-
-		linewidth: {
-
-			enumerable: true,
-
-			get: function () {
-
-				return this.uniforms.linewidth.value;
-
-			},
+	};
 
-			set: function ( value ) {
+	class LineMaterial extends THREE.ShaderMaterial {
 
-				this.uniforms.linewidth.value = value;
+		constructor( parameters ) {
 
-			}
+			super( {
+				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;
+			Object.defineProperties( this, {
+				color: {
+					enumerable: true,
+					get: function () {
 
-		dashScale: {
+						return this.uniforms.diffuse.value;
 
-			enumerable: true,
+					},
+					set: function ( value ) {
 
-			get: function () {
+						this.uniforms.diffuse.value = value;
 
-				return this.uniforms.dashScale.value;
+					}
+				},
+				linewidth: {
+					enumerable: true,
+					get: function () {
 
-			},
+						return this.uniforms.linewidth.value;
 
-			set: function ( value ) {
+					},
+					set: function ( value ) {
 
-				this.uniforms.dashScale.value = value;
+						this.uniforms.linewidth.value = value;
 
-			}
+					}
+				},
+				dashScale: {
+					enumerable: true,
+					get: function () {
 
-		},
+						return this.uniforms.dashScale.value;
 
-		dashSize: {
+					},
+					set: function ( value ) {
 
-			enumerable: true,
+						this.uniforms.dashScale.value = value;
 
-			get: function () {
+					}
+				},
+				dashSize: {
+					enumerable: true,
+					get: function () {
 
-				return this.uniforms.dashSize.value;
+						return this.uniforms.dashSize.value;
 
-			},
+					},
+					set: function ( value ) {
 
-			set: function ( value ) {
+						this.uniforms.dashSize.value = value;
 
-				this.uniforms.dashSize.value = value;
+					}
+				},
+				dashOffset: {
+					enumerable: true,
+					get: function () {
 
-			}
+						return this.uniforms.dashOffset.value;
 
-		},
+					},
+					set: function ( value ) {
 
-		dashOffset: {
+						this.uniforms.dashOffset.value = value;
 
-			enumerable: true,
+					}
+				},
+				gapSize: {
+					enumerable: true,
+					get: function () {
 
-			get: function () {
+						return this.uniforms.gapSize.value;
 
-				return this.uniforms.dashOffset.value;
+					},
+					set: function ( value ) {
 
-			},
+						this.uniforms.gapSize.value = value;
 
-			set: function ( value ) {
+					}
+				},
+				opacity: {
+					enumerable: true,
+					get: function () {
 
-				this.uniforms.dashOffset.value = value;
+						return this.uniforms.opacity.value;
 
-			}
+					},
+					set: function ( value ) {
 
-		},
+						this.uniforms.opacity.value = value;
 
-		gapSize: {
+					}
+				},
+				resolution: {
+					enumerable: true,
+					get: function () {
 
-			enumerable: true,
+						return this.uniforms.resolution.value;
 
-			get: function () {
+					},
+					set: function ( value ) {
 
-				return this.uniforms.gapSize.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.gapSize.value = value;
+					},
+					set: function ( value ) {
 
-			}
+						if ( Boolean( value ) !== Boolean( 'ALPHA_TO_COVERAGE' in this.defines ) ) {
 
-		},
-
-		opacity: {
-
-			enumerable: true,
-
-			get: function () {
-
-				return this.uniforms.opacity.value;
+							this.needsUpdate = true;
 
-			},
+						}
 
-			set: function ( value ) {
+						if ( value ) {
 
-				this.uniforms.opacity.value = value;
-
-			}
-
-		},
-
-		resolution: {
+							this.defines.ALPHA_TO_COVERAGE = '';
+							this.extensions.derivatives = true;
 
-			enumerable: true,
+						} else {
 
-			get: function () {
+							delete this.defines.ALPHA_TO_COVERAGE;
+							this.extensions.derivatives = false;
 
-				return this.uniforms.resolution.value;
-
-			},
-
-			set: function ( value ) {
-
-				this.uniforms.resolution.value.copy( value );
-
-			}
-
-		},
-
-		alphaToCoverage: {
-
-			enumerable: true,
-
-			get: function () {
-
-				return Boolean( 'ALPHA_TO_COVERAGE' in this.defines );
-
-			},
-
-			set: function ( value ) {
-
-				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.isLineMaterial = true;
 
-THREE.LineMaterial.prototype = Object.create( THREE.ShaderMaterial.prototype );
-THREE.LineMaterial.prototype.constructor = THREE.LineMaterial;
+	THREE.LineMaterial = LineMaterial;
 
-THREE.LineMaterial.prototype.isLineMaterial = true;
+} )();

+ 166 - 163
examples/js/lines/LineSegments2.js

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

+ 104 - 122
examples/js/lines/LineSegmentsGeometry.js

@@ -1,159 +1,147 @@
-THREE.LineSegmentsGeometry = function () {
+( function () {
 
-	THREE.InstancedBufferGeometry.call( this );
+	const _box = new THREE.Box3();
 
-	this.type = 'LineSegmentsGeometry';
+	const _vector = new THREE.Vector3();
 
-	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 ];
+	class LineSegmentsGeometry extends THREE.InstancedBufferGeometry {
 
-	this.setIndex( index );
-	this.setAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
-	this.setAttribute( 'uv', new THREE.Float32BufferAttribute( uvs, 2 ) );
+		constructor() {
 
-};
+			super();
+			this.type = 'LineSegmentsGeometry';
+			const 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 ];
+			const uvs = [ - 1, 2, 1, 2, - 1, 1, 1, 1, - 1, - 1, 1, - 1, - 1, - 2, 1, - 2 ];
+			const 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 ) );
 
-THREE.LineSegmentsGeometry.prototype = Object.assign( Object.create( THREE.InstancedBufferGeometry.prototype ), {
+		}
 
-	constructor: THREE.LineSegmentsGeometry,
+		applyMatrix4( matrix ) {
 
-	isLineSegmentsGeometry: true,
+			const start = this.attributes.instanceStart;
+			const end = this.attributes.instanceEnd;
 
-	applyMatrix4: function ( matrix ) {
+			if ( start !== undefined ) {
 
-		var start = this.attributes.instanceStart;
-		var end = this.attributes.instanceEnd;
+				start.applyMatrix4( matrix );
+				end.applyMatrix4( matrix );
+				start.needsUpdate = true;
 
-		if ( start !== undefined ) {
+			}
 
-			start.applyMatrix4( matrix );
+			if ( this.boundingBox !== null ) {
 
-			end.applyMatrix4( matrix );
+				this.computeBoundingBox();
 
-			start.needsUpdate = true;
+			}
 
-		}
+			if ( this.boundingSphere !== null ) {
 
-		if ( this.boundingBox !== null ) {
+				this.computeBoundingSphere();
 
-			this.computeBoundingBox();
+			}
+
+			return this;
 
 		}
 
-		if ( this.boundingSphere !== null ) {
+		setPositions( array ) {
 
-			this.computeBoundingSphere();
+			let lineSegments;
 
-		}
+			if ( array instanceof Float32Array ) {
 
-		return this;
+				lineSegments = array;
 
-	},
+			} else if ( Array.isArray( array ) ) {
 
-	setPositions: function ( array ) {
+				lineSegments = new Float32Array( array );
 
-		var lineSegments;
+			}
 
-		if ( array instanceof Float32Array ) {
+			const instanceBuffer = new THREE.InstancedInterleavedBuffer( lineSegments, 6, 1 ); // xyz, xyz
 
-			lineSegments = array;
+			this.setAttribute( 'instanceStart', new THREE.InterleavedBufferAttribute( instanceBuffer, 3, 0 ) ); // xyz
 
-		} else if ( Array.isArray( array ) ) {
+			this.setAttribute( 'instanceEnd', new THREE.InterleavedBufferAttribute( instanceBuffer, 3, 3 ) ); // xyz
+			//
 
-			lineSegments = new Float32Array( array );
+			this.computeBoundingBox();
+			this.computeBoundingSphere();
+			return this;
 
 		}
 
-		var instanceBuffer = new THREE.InstancedInterleavedBuffer( lineSegments, 6, 1 ); // xyz, xyz
+		setColors( array ) {
 
-		this.setAttribute( 'instanceStart', new THREE.InterleavedBufferAttribute( instanceBuffer, 3, 0 ) ); // xyz
-		this.setAttribute( 'instanceEnd', new THREE.InterleavedBufferAttribute( instanceBuffer, 3, 3 ) ); // xyz
+			let colors;
 
-		//
+			if ( array instanceof Float32Array ) {
 
-		this.computeBoundingBox();
-		this.computeBoundingSphere();
+				colors = array;
 
-		return this;
+			} else if ( Array.isArray( array ) ) {
 
-	},
+				colors = new Float32Array( array );
 
-	setColors: function ( array ) {
-
-		var colors;
+			}
 
-		if ( array instanceof Float32Array ) {
+			const instanceColorBuffer = new THREE.InstancedInterleavedBuffer( colors, 6, 1 ); // rgb, rgb
 
-			colors = array;
+			this.setAttribute( 'instanceColorStart', new THREE.InterleavedBufferAttribute( instanceColorBuffer, 3, 0 ) ); // rgb
 
-		} else if ( Array.isArray( array ) ) {
+			this.setAttribute( 'instanceColorEnd', new THREE.InterleavedBufferAttribute( instanceColorBuffer, 3, 3 ) ); // rgb
 
-			colors = new Float32Array( array );
+			return this;
 
 		}
 
-		var instanceColorBuffer = new THREE.InstancedInterleavedBuffer( colors, 6, 1 ); // rgb, rgb
+		fromWireframeGeometry( geometry ) {
 
-		this.setAttribute( 'instanceColorStart', new THREE.InterleavedBufferAttribute( instanceColorBuffer, 3, 0 ) ); // rgb
-		this.setAttribute( 'instanceColorEnd', new THREE.InterleavedBufferAttribute( instanceColorBuffer, 3, 3 ) ); // rgb
+			this.setPositions( geometry.attributes.position.array );
+			return this;
 
-		return this;
-
-	},
-
-	fromWireframeGeometry: function ( geometry ) {
-
-		this.setPositions( geometry.attributes.position.array );
-
-		return this;
+		}
 
-	},
+		fromEdgesGeometry( geometry ) {
 
-	fromEdgesGeometry: function ( geometry ) {
+			this.setPositions( geometry.attributes.position.array );
+			return this;
 
-		this.setPositions( geometry.attributes.position.array );
+		}
 
-		return this;
+		fromMesh( mesh ) {
 
-	},
+			this.fromWireframeGeometry( new THREE.WireframeGeometry( mesh.geometry ) ); // set colors, maybe
 
-	fromMesh: function ( mesh ) {
+			return this;
 
-		this.fromWireframeGeometry( new THREE.WireframeGeometry( mesh.geometry ) );
+		}
 
-		// set colors, maybe
+		romLineSegments( lineSegments ) {
 
-		return this;
+			const geometry = lineSegments.geometry;
 
-	},
+			if ( geometry.isGeometry ) {
 
-	fromLineSegments: function ( lineSegments ) {
+				console.error( 'THREE.LineSegmentsGeometry no longer supports Geometry. Use THREE.BufferGeometry instead.' );
+				return;
 
-		var geometry = lineSegments.geometry;
+			} else if ( geometry.isBufferGeometry ) {
 
-		if ( geometry.isGeometry ) {
+				this.setPositions( geometry.attributes.position.array ); // assumes non-indexed
 
-			console.error( 'THREE.LineSegmentsGeometry no longer supports THREE.Geometry. Use THREE.BufferGeometry instead.' );
-			return;
+			} // set colors, maybe
 
-		} else if ( geometry.isBufferGeometry ) {
 
-			this.setPositions( geometry.attributes.position.array ); // assumes non-indexed
+			return this;
 
 		}
 
-		// set colors, maybe
-
-		return this;
-
-	},
-
-	computeBoundingBox: function () {
-
-		var box = new THREE.Box3();
-
-		return function computeBoundingBox() {
+		computeBoundingBox() {
 
 			if ( this.boundingBox === null ) {
 
@@ -161,28 +149,22 @@ THREE.LineSegmentsGeometry.prototype = Object.assign( Object.create( THREE.Insta
 
 			}
 
-			var start = this.attributes.instanceStart;
-			var end = this.attributes.instanceEnd;
+			const start = this.attributes.instanceStart;
+			const end = this.attributes.instanceEnd;
 
 			if ( start !== undefined && end !== undefined ) {
 
 				this.boundingBox.setFromBufferAttribute( start );
 
-				box.setFromBufferAttribute( end );
+				_box.setFromBufferAttribute( end );
 
-				this.boundingBox.union( box );
+				this.boundingBox.union( _box );
 
 			}
 
-		};
-
-	}(),
-
-	computeBoundingSphere: function () {
-
-		var vector = new THREE.Vector3();
+		}
 
-		return function computeBoundingSphere() {
+		computeBoundingSphere() {
 
 			if ( this.boundingSphere === null ) {
 
@@ -196,24 +178,24 @@ THREE.LineSegmentsGeometry.prototype = Object.assign( Object.create( THREE.Insta
 
 			}
 
-			var start = this.attributes.instanceStart;
-			var end = this.attributes.instanceEnd;
+			const start = this.attributes.instanceStart;
+			const end = this.attributes.instanceEnd;
 
 			if ( start !== undefined && end !== undefined ) {
 
-				var center = this.boundingSphere.center;
-
+				const center = this.boundingSphere.center;
 				this.boundingBox.getCenter( center );
+				let maxRadiusSq = 0;
+
+				for ( let i = 0, il = start.count; i < il; i ++ ) {
 
-				var maxRadiusSq = 0;
+					_vector.fromBufferAttribute( start, i );
 
-				for ( var i = 0, il = start.count; i < il; i ++ ) {
+					maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector ) );
 
-					vector.fromBufferAttribute( start, i );
-					maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
+					_vector.fromBufferAttribute( end, i );
 
-					vector.fromBufferAttribute( end, i );
-					maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( vector ) );
+					maxRadiusSq = Math.max( maxRadiusSq, center.distanceToSquared( _vector ) );
 
 				}
 
@@ -227,22 +209,22 @@ THREE.LineSegmentsGeometry.prototype = Object.assign( Object.create( THREE.Insta
 
 			}
 
-		};
-
-	}(),
+		}
 
-	toJSON: function () {
+		toJSON() { // todo
+		}
 
-		// todo
+		applyMatrix( 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 );
+	LineSegmentsGeometry.prototype.isLineSegmentsGeometry = true;
 
-	}
+	THREE.LineSegmentsGeometry = LineSegmentsGeometry;
 
-} );
+} )();

+ 29 - 28
examples/js/lines/Wireframe.js

@@ -1,52 +1,53 @@
-THREE.Wireframe = function ( geometry, material ) {
+( function () {
 
-	THREE.Mesh.call( this );
+	const _start = new THREE.Vector3();
 
-	this.type = 'Wireframe';
+	const _end = new THREE.Vector3();
 
-	this.geometry = geometry !== undefined ? geometry : new THREE.LineSegmentsGeometry();
-	this.material = material !== undefined ? material : new THREE.LineMaterial( { color: Math.random() * 0xffffff } );
+	class Wireframe extends THREE.Mesh {
 
-};
+		constructor( geometry = new THREE.LineSegmentsGeometry(), material = new THREE.LineMaterial( {
+			color: Math.random() * 0xffffff
+		} ) ) {
 
-THREE.Wireframe.prototype = Object.assign( Object.create( THREE.Mesh.prototype ), {
+			super( geometry, material );
+			this.type = 'Wireframe';
 
-	constructor: THREE.Wireframe,
+		} // for backwards-compatability, but could be a method of THREE.LineSegmentsGeometry...
 
-	isWireframe: true,
 
-	computeLineDistances: ( function () { // for backwards-compatability, but could be a method of LineSegmentsGeometry...
+		computeLineDistances() {
 
-		var start = new THREE.Vector3();
-		var end = new THREE.Vector3();
+			const geometry = this.geometry;
+			const instanceStart = geometry.attributes.instanceStart;
+			const instanceEnd = geometry.attributes.instanceEnd;
+			const lineDistances = new Float32Array( 2 * instanceStart.count );
 
-		return function computeLineDistances() {
+			for ( let i = 0, j = 0, l = instanceStart.count; i < l; i ++, j += 2 ) {
 
-			var geometry = this.geometry;
+				_start.fromBufferAttribute( instanceStart, i );
 
-			var instanceStart = geometry.attributes.instanceStart;
-			var instanceEnd = geometry.attributes.instanceEnd;
-			var lineDistances = new Float32Array( 2 * instanceStart.count );
+				_end.fromBufferAttribute( instanceEnd, i );
 
-			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 );
+				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
+			const 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;
 
-		};
+		}
+
+	}
+
+	Wireframe.prototype.isWireframe = true;
 
-	}() )
+	THREE.Wireframe = Wireframe;
 
-} );
+} )();

+ 11 - 11
examples/js/lines/WireframeGeometry2.js

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

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

@@ -0,0 +1,1283 @@
+( function () {
+
+	const _taskCache = new WeakMap();
+
+	class Rhino3dmLoader extends THREE.Loader {
+
+		constructor( manager ) {
+
+			super( 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 = [];
+
+		}
+
+		setLibraryPath( path ) {
+
+			this.libraryPath = path;
+			return this;
+
+		}
+
+		setWorkerLimit( workerLimit ) {
+
+			this.workerLimit = workerLimit;
+			return this;
+
+		}
+
+		load( url, onLoad, onProgress, onError ) {
+
+			const 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 ( _taskCache.has( buffer ) ) {
+
+					const cachedTask = _taskCache.get( buffer );
+
+					return cachedTask.promise.then( onLoad ).catch( onError );
+
+				}
+
+				this.decodeObjects( buffer, url ).then( onLoad ).catch( onError );
+
+			}, onProgress, onError );
+
+		}
+
+		debug() {
+
+			console.log( 'Task load: ', this.workerPool.map( worker => worker._taskLoad ) );
+
+		}
+
+		decodeObjects( buffer, url ) {
+
+			let worker;
+			let taskID;
+			const taskCost = buffer.byteLength;
+
+			const 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.
+
+			_taskCache.set( buffer, {
+				url: url,
+				promise: objectPending
+			} );
+
+			return objectPending;
+
+		}
+
+		parse( data, onLoad, onError ) {
+
+			this.decodeObjects( data, '' ).then( onLoad ).catch( onError );
+
+		}
+
+		_compareMaterials( material ) {
+
+			const 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 ( let i = 0; i < this.materials.length; i ++ ) {
+
+				const m = this.materials[ i ];
+				const _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( material ) {
+
+			if ( material === undefined ) {
+
+				return new THREE.MeshStandardMaterial( {
+					color: new THREE.Color( 1, 1, 1 ),
+					metalness: 0.8,
+					name: 'default',
+					side: 2
+				} );
+
+			}
+
+			const _diffuseColor = material.diffuseColor;
+			const 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 );
+
+
+			const mat = new THREE.MeshStandardMaterial( {
+				color: diffusecolor,
+				name: material.name,
+				side: 2,
+				transparent: material.transparency > 0 ? true : false,
+				opacity: 1.0 - material.transparency
+			} );
+			const textureLoader = new THREE.TextureLoader();
+
+			for ( let i = 0; i < material.textures.length; i ++ ) {
+
+				const texture = material.textures[ i ];
+
+				if ( texture.image !== null ) {
+
+					const 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( data ) {
+
+			// console.log(data);
+			const object = new THREE.Object3D();
+			const instanceDefinitionObjects = [];
+			const instanceDefinitions = [];
+			const 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;
+			let objects = data.objects;
+			const materials = data.materials;
+
+			for ( let i = 0; i < objects.length; i ++ ) {
+
+				const obj = objects[ i ];
+				const attributes = obj.attributes;
+
+				switch ( obj.objectType ) {
+
+					case 'InstanceDefinition':
+						instanceDefinitions.push( obj );
+						break;
+
+					case 'InstanceReference':
+						instanceReferences.push( obj );
+						break;
+
+					default:
+						let _object;
+
+						if ( attributes.materialIndex >= 0 ) {
+
+							const rMaterial = materials[ attributes.materialIndex ];
+
+							let material = this._createMaterial( rMaterial );
+
+							material = this._compareMaterials( material );
+							_object = this._createObject( obj, material );
+
+						} else {
+
+							const material = this._createMaterial();
+
+							_object = this._createObject( obj, material );
+
+						}
+
+						if ( _object === undefined ) {
+
+							continue;
+
+						}
+
+						const 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 ( let i = 0; i < instanceDefinitions.length; i ++ ) {
+
+				const iDef = instanceDefinitions[ i ];
+				objects = [];
+
+				for ( let j = 0; j < iDef.attributes.objectIds.length; j ++ ) {
+
+					const objId = iDef.attributes.objectIds[ j ];
+
+					for ( let p = 0; p < instanceDefinitionObjects.length; p ++ ) {
+
+						const idoId = instanceDefinitionObjects[ p ].userData.attributes.id;
+
+						if ( objId === idoId ) {
+
+							objects.push( instanceDefinitionObjects[ p ] );
+
+						}
+
+					}
+
+				} // Currently clones geometry and does not take advantage of instancing
+
+
+				for ( let j = 0; j < instanceReferences.length; j ++ ) {
+
+					const iRef = instanceReferences[ j ];
+
+					if ( iRef.geometry.parentIdefId === iDef.attributes.id ) {
+
+						const iRefObject = new THREE.Object3D();
+						const xf = iRef.geometry.xform.array;
+						const 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 ( let p = 0; p < objects.length; p ++ ) {
+
+							iRefObject.add( objects[ p ].clone( true ) );
+
+						}
+
+						object.add( iRefObject );
+
+					}
+
+				}
+
+			}
+
+			object.userData[ 'materials' ] = this.materials;
+			return object;
+
+		}
+
+		_createObject( obj, mat ) {
+
+			const loader = new THREE.BufferGeometryLoader();
+			const attributes = obj.attributes;
+
+			let geometry, material, _color, color;
+
+			switch ( obj.objectType ) {
+
+				case 'Point':
+				case 'PointSet':
+					geometry = loader.parse( obj.geometry );
+
+					if ( geometry.attributes.hasOwnProperty( 'color' ) ) {
+
+						material = new THREE.PointsMaterial( {
+							vertexColors: true,
+							sizeAttenuation: false,
+							size: 2
+						} );
+
+					} else {
+
+						_color = attributes.drawColor;
+						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 );
+					const 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;
+					geometry = loader.parse( obj.geometry );
+
+					if ( geometry.attributes.hasOwnProperty( 'color' ) ) {
+
+						mat.vertexColors = true;
+
+					}
+
+					if ( mat === null ) {
+
+						mat = this._createMaterial();
+						mat = this._compareMaterials( mat );
+
+					}
+
+					const 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 );
+					_color = attributes.drawColor;
+					color = new THREE.Color( _color.r / 255.0, _color.g / 255.0, _color.b / 255.0 );
+					material = new THREE.LineBasicMaterial( {
+						color: color
+					} );
+					material = this._compareMaterials( material );
+					const 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;
+					const ctx = document.createElement( 'canvas' ).getContext( '2d' );
+					const font = `${geometry.fontHeight}px ${geometry.fontFace}`;
+					ctx.font = font;
+					const width = ctx.measureText( geometry.text ).width + 10;
+					const height = geometry.fontHeight + 10;
+					const 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';
+					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 );
+					const texture = new THREE.CanvasTexture( ctx.canvas );
+					texture.minFilter = THREE.LinearFilter;
+					texture.wrapS = THREE.ClampToEdgeWrapping;
+					texture.wrapT = THREE.ClampToEdgeWrapping;
+					material = new THREE.SpriteMaterial( {
+						map: texture,
+						depthTest: false
+					} );
+					const 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;
+					let 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();
+						const width = Math.abs( geometry.width[ 2 ] );
+						const 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;
+						_color = geometry.diffuse;
+						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() {
+
+			if ( ! this.libraryPending ) {
+
+				// Load rhino3dm wrapper.
+				const jsLoader = new THREE.FileLoader( this.manager );
+				jsLoader.setPath( this.libraryPath );
+				const jsContent = new Promise( ( resolve, reject ) => {
+
+					jsLoader.load( 'rhino3dm.js', resolve, undefined, reject );
+
+				} ); // Load rhino3dm WASM binary.
+
+				const binaryLoader = new THREE.FileLoader( this.manager );
+				binaryLoader.setPath( this.libraryPath );
+				binaryLoader.setResponseType( 'arraybuffer' );
+				const 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;
+					const fn = Rhino3dmWorker.toString();
+					const 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( taskCost ) {
+
+			return this._initLibrary().then( () => {
+
+				if ( this.workerPool.length < this.workerLimit ) {
+
+					const worker = new Worker( this.workerSourceURL );
+					worker._callbacks = {};
+					worker._taskCosts = {};
+					worker._taskLoad = 0;
+					worker.postMessage( {
+						type: 'init',
+						libraryConfig: this.libraryConfig
+					} );
+
+					worker.onmessage = function ( e ) {
+
+						const 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;
+
+					} );
+
+				}
+
+				const worker = this.workerPool[ this.workerPool.length - 1 ];
+				worker._taskLoad += taskCost;
+				return worker;
+
+			} );
+
+		}
+
+		_releaseTask( worker, taskID ) {
+
+			worker._taskLoad -= worker._taskCosts[ taskID ];
+			delete worker._callbacks[ taskID ];
+			delete worker._taskCosts[ taskID ];
+
+		}
+
+		dispose() {
+
+			for ( let i = 0; i < this.workerPool.length; ++ i ) {
+
+				this.workerPool[ i ].terminate();
+
+			}
+
+			this.workerPool.length = 0;
+			return this;
+
+		}
+
+	}
+	/* WEB WORKER */
+
+
+	function Rhino3dmWorker() {
+
+		let libraryPending;
+		let libraryConfig;
+		let rhino;
+
+		onmessage = function ( e ) {
+
+			const message = e.data;
+
+			switch ( message.type ) {
+
+				case 'init':
+					libraryConfig = message.libraryConfig;
+					const wasmBinary = libraryConfig.wasmBinary;
+					let 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':
+					const buffer = message.buffer;
+					libraryPending.then( () => {
+
+						const data = decodeObjects( rhino, buffer );
+						self.postMessage( {
+							type: 'decode',
+							id: message.id,
+							data
+						} );
+
+					} );
+					break;
+
+			}
+
+		};
+
+		function decodeObjects( rhino, buffer ) {
+
+			const arr = new Uint8Array( buffer );
+			const doc = rhino.File3dm.fromByteArray( arr );
+			const objects = [];
+			const materials = [];
+			const layers = [];
+			const views = [];
+			const namedViews = [];
+			const groups = []; //Handle objects
+
+			const objs = doc.objects();
+			const cnt = objs.count;
+
+			for ( let i = 0; i < cnt; i ++ ) {
+
+				const _object = objs.get( i );
+
+				const object = extractObjectData( _object, doc );
+
+				_object.delete();
+
+				if ( object ) {
+
+					objects.push( object );
+
+				}
+
+			} // Handle instance definitions
+			// console.log( `Instance Definitions Count: ${doc.instanceDefinitions().count()}` );
+
+
+			for ( let i = 0; i < doc.instanceDefinitions().count(); i ++ ) {
+
+				const idef = doc.instanceDefinitions().get( i );
+				const idefAttributes = extractProperties( idef );
+				idefAttributes.objectIds = idef.getObjectIds();
+				objects.push( {
+					geometry: null,
+					attributes: idefAttributes,
+					objectType: 'InstanceDefinition'
+				} );
+
+			} // Handle materials
+
+
+			const textureTypes = [// rhino.TextureType.Bitmap,
+				rhino.TextureType.Diffuse, rhino.TextureType.Bump, rhino.TextureType.Transparency, rhino.TextureType.Opacity, rhino.TextureType.Emap ];
+			const 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 ( let i = 0; i < doc.materials().count(); i ++ ) {
+
+				const _material = doc.materials().get( i );
+
+				const _pbrMaterial = _material.physicallyBased();
+
+				let material = extractProperties( _material );
+				const textures = [];
+
+				for ( let j = 0; j < textureTypes.length; j ++ ) {
+
+					const _texture = _material.getTexture( textureTypes[ j ] );
+
+					if ( _texture ) {
+
+						let textureType = textureTypes[ j ].constructor.name;
+						textureType = textureType.substring( 12, textureType.length );
+						const texture = {
+							type: textureType
+						};
+						const 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 ( let j = 0; j < pbrTextureTypes.length; j ++ ) {
+
+						const _texture = _material.getTexture( textureTypes[ j ] );
+
+						if ( _texture ) {
+
+							const image = doc.getEmbeddedFileAsBase64( _texture.fileName );
+							let textureType = textureTypes[ j ].constructor.name;
+							textureType = textureType.substring( 12, textureType.length );
+							const texture = {
+								type: textureType,
+								image: 'data:image/png;base64,' + image
+							};
+							textures.push( texture );
+
+							_texture.delete();
+
+						}
+
+					}
+
+					const pbMaterialProperties = extractProperties( _material.physicallyBased() );
+					material = Object.assign( pbMaterialProperties, material );
+
+				}
+
+				materials.push( material );
+
+				_material.delete();
+
+				_pbrMaterial.delete();
+
+			} // Handle layers
+
+
+			for ( let i = 0; i < doc.layers().count(); i ++ ) {
+
+				const _layer = doc.layers().get( i );
+
+				const layer = extractProperties( _layer );
+				layers.push( layer );
+
+				_layer.delete();
+
+			} // Handle views
+
+
+			for ( let i = 0; i < doc.views().count(); i ++ ) {
+
+				const _view = doc.views().get( i );
+
+				const view = extractProperties( _view );
+				views.push( view );
+
+				_view.delete();
+
+			} // Handle named views
+
+
+			for ( let i = 0; i < doc.namedViews().count(); i ++ ) {
+
+				const _namedView = doc.namedViews().get( i );
+
+				const namedView = extractProperties( _namedView );
+				namedViews.push( namedView );
+
+				_namedView.delete();
+
+			} // Handle groups
+
+
+			for ( let i = 0; i < doc.groups().count(); i ++ ) {
+
+				const _group = doc.groups().get( i );
+
+				const group = extractProperties( _group );
+				groups.push( group );
+
+				_group.delete();
+
+			} // Handle settings
+
+
+			const 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 ) {
+
+			const _geometry = object.geometry();
+
+			const _attributes = object.attributes();
+
+			let objectType = _geometry.objectType;
+			let geometry, attributes, position, data, mesh; // skip instance definition objects
+			//if( _attributes.isInstanceDefinitionObject ) { continue; }
+			// TODO: handle other geometry types
+
+			switch ( objectType ) {
+
+				case rhino.ObjectType.Curve:
+					const pts = curveToPoints( _geometry, 100 );
+					position = {};
+					attributes = {};
+					data = {};
+					position.itemSize = 3;
+					position.type = 'Float32Array';
+					position.array = [];
+
+					for ( let 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:
+					const pt = _geometry.location;
+					position = {};
+					const color = {};
+					attributes = {};
+					data = {};
+					position.itemSize = 3;
+					position.type = 'Float32Array';
+					position.array = [ pt[ 0 ], pt[ 1 ], pt[ 2 ] ];
+
+					const _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:
+					const faces = _geometry.faces();
+
+					mesh = new rhino.Mesh();
+
+					for ( let faceIndex = 0; faceIndex < faces.count; faceIndex ++ ) {
+
+						const face = faces.get( faceIndex );
+
+						const _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:
+					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 );
+
+					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 ) {
+
+				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 ) {
+
+			const result = {};
+
+			for ( const property in object ) {
+
+				const 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 ) {
+
+			let pointCount = pointLimit;
+			let rc = [];
+			const ts = [];
+
+			if ( curve instanceof rhino.LineCurve ) {
+
+				return [ curve.pointAtStart, curve.pointAtEnd ];
+
+			}
+
+			if ( curve instanceof rhino.PolylineCurve ) {
+
+				pointCount = curve.pointCount;
+
+				for ( let i = 0; i < pointCount; i ++ ) {
+
+					rc.push( curve.point( i ) );
+
+				}
+
+				return rc;
+
+			}
+
+			if ( curve instanceof rhino.PolyCurve ) {
+
+				const segmentCount = curve.segmentCount;
+
+				for ( let i = 0; i < segmentCount; i ++ ) {
+
+					const segment = curve.segmentCurve( i );
+					const 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 ( let i = 0; i < pLine.count; i ++ ) {
+
+					rc.push( pLine.get( i ) );
+
+				}
+
+				pLine.delete();
+				return rc;
+
+			}
+
+			const domain = curve.domain;
+			const divisions = pointCount - 1.0;
+
+			for ( let j = 0; j < pointCount; j ++ ) {
+
+				const t = domain[ 0 ] + j / divisions * ( domain[ 1 ] - domain[ 0 ] );
+
+				if ( t === domain[ 0 ] || t === domain[ 1 ] ) {
+
+					ts.push( t );
+					continue;
+
+				}
+
+				const tan = curve.tangentAt( t );
+				const prevTan = curve.tangentAt( ts.slice( - 1 )[ 0 ] ); // Duplicated from THREE.Vector3
+				// How to pass imports to worker?
+
+				const tS = tan[ 0 ] * tan[ 0 ] + tan[ 1 ] * tan[ 1 ] + tan[ 2 ] * tan[ 2 ];
+				const ptS = prevTan[ 0 ] * prevTan[ 0 ] + prevTan[ 1 ] * prevTan[ 1 ] + prevTan[ 2 ] * prevTan[ 2 ];
+				const denominator = Math.sqrt( tS * ptS );
+				let angle;
+
+				if ( denominator === 0 ) {
+
+					angle = Math.PI / 2;
+
+				} else {
+
+					const 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;
+
+} )();

+ 798 - 908
examples/js/loaders/3MFLoader.js

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

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

@@ -1,11 +1,13 @@
-/**
- * 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
  *
  * Usage:
- *	var loader = new AMFLoader();
+ *	const loader = new AMFLoader();
  *	loader.load('/path/to/project.amf', function(objecttree) {
  *		scene.add(objecttree);
  *	});
@@ -16,492 +18,514 @@
  *
  */
 
-THREE.AMFLoader = function ( manager ) {
+	class AMFLoader extends THREE.Loader {
 
-	THREE.Loader.call( this, manager );
+		constructor( manager ) {
 
-};
+			super( manager );
 
-THREE.AMFLoader.prototype = Object.assign( Object.create( THREE.Loader.prototype ), {
+		}
 
-	constructor: THREE.AMFLoader,
+		load( url, onLoad, onProgress, onError ) {
 
-	load: function ( url, onLoad, onProgress, onError ) {
+			const scope = this;
+			const loader = new THREE.FileLoader( scope.manager );
+			loader.setPath( scope.path );
+			loader.setResponseType( 'arraybuffer' );
+			loader.setRequestHeader( scope.requestHeader );
+			loader.setWithCredentials( scope.withCredentials );
+			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( data ) {
 
-		function loadDocument( data ) {
+			function loadDocument( data ) {
 
-			var view = new DataView( data );
-			var magic = String.fromCharCode( view.getUint8( 0 ), view.getUint8( 1 ) );
+				let view = new DataView( data );
+				const magic = String.fromCharCode( view.getUint8( 0 ), view.getUint8( 1 ) );
 
-			if ( magic === 'PK' ) {
+				if ( magic === 'PK' ) {
 
-				var zip = null;
-				var file = null;
+					let zip = null;
+					let 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 ( 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 );
+				const fileText = THREE.LoaderUtils.decodeText( view );
+				const 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 ) {
 
-		}
+				let scale = 1.0;
+				let 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();
+				const 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 ) {
 
-		}
+				let matName = 'AMF Material';
+				const matId = node.attributes.id.textContent;
+				let color = {
+					r: 1.0,
+					g: 1.0,
+					b: 1.0,
+					a: 1.0
+				};
+				let loadedMaterial = null;
 
-		function loadMaterials( node ) {
+				for ( let 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 };
+					const 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 ) {
 
-		}
+				const color = {
+					r: 1.0,
+					g: 1.0,
+					b: 1.0,
+					a: 1.0
+				};
 
-		function loadColor( node ) {
+				for ( let i = 0; i < node.childNodes.length; i ++ ) {
 
-			var color = { r: 1.0, g: 1.0, b: 1.0, a: 1.0 };
+					const matColor = node.childNodes[ i ];
 
-			for ( var i = 0; i < node.childNodes.length; i ++ ) {
+					if ( matColor.nodeName === 'r' ) {
 
-				var matColor = node.childNodes[ i ];
+						color.r = matColor.textContent;
 
-				if ( matColor.nodeName === 'r' ) {
+					} else if ( matColor.nodeName === 'g' ) {
 
-					color.r = matColor.textContent;
+						color.g = matColor.textContent;
 
-				} else if ( matColor.nodeName === 'g' ) {
+					} else if ( matColor.nodeName === 'b' ) {
 
-					color.g = matColor.textContent;
+						color.b = matColor.textContent;
 
-				} else if ( matColor.nodeName === 'b' ) {
+					} else if ( matColor.nodeName === 'a' ) {
 
-					color.b = matColor.textContent;
+						color.a = matColor.textContent;
 
-				} else if ( matColor.nodeName === 'a' ) {
-
-					color.a = matColor.textContent;
+					}
 
 				}
 
-			}
-
-			return color;
+				return color;
 
-		}
+			}
 
-		function loadMeshVolume( node ) {
+			function loadMeshVolume( node ) {
 
-			var volume = { name: '', triangles: [], materialid: null };
+				const volume = {
+					name: '',
+					triangles: [],
+					materialid: null
+				};
+				let 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' ) {
+						const v1 = currVolumeNode.getElementsByTagName( 'v1' )[ 0 ].textContent;
+						const v2 = currVolumeNode.getElementsByTagName( 'v2' )[ 0 ].textContent;
+						const 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 ) {
 
-		}
+				const vertArray = [];
+				const normalArray = [];
+				let currVerticesNode = node.firstElementChild;
 
-		function loadMeshVertices( node ) {
+				while ( currVerticesNode ) {
 
-			var vertArray = [];
-			var normalArray = [];
-			var currVerticesNode = node.firstElementChild;
+					if ( currVerticesNode.nodeName === 'vertex' ) {
 
-			while ( currVerticesNode ) {
+						let vNode = currVerticesNode.firstElementChild;
 
-				if ( currVerticesNode.nodeName === 'vertex' ) {
+						while ( vNode ) {
 
-					var vNode = currVerticesNode.firstElementChild;
+							if ( vNode.nodeName === 'coordinates' ) {
 
-					while ( vNode ) {
+								const x = vNode.getElementsByTagName( 'x' )[ 0 ].textContent;
+								const y = vNode.getElementsByTagName( 'y' )[ 0 ].textContent;
+								const z = vNode.getElementsByTagName( 'z' )[ 0 ].textContent;
+								vertArray.push( x, y, z );
 
-						if ( vNode.nodeName === 'coordinates' ) {
+							} else if ( vNode.nodeName === 'normal' ) {
 
-							var x = vNode.getElementsByTagName( 'x' )[ 0 ].textContent;
-							var y = vNode.getElementsByTagName( 'y' )[ 0 ].textContent;
-							var z = vNode.getElementsByTagName( 'z' )[ 0 ].textContent;
+								const nx = vNode.getElementsByTagName( 'nx' )[ 0 ].textContent;
+								const ny = vNode.getElementsByTagName( 'ny' )[ 0 ].textContent;
+								const nz = vNode.getElementsByTagName( 'nz' )[ 0 ].textContent;
+								normalArray.push( nx, ny, nz );
 
-							vertArray.push( x, y, z );
+							}
 
-						} 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 ) {
+				const objId = node.attributes.id.textContent;
+				const loadedObject = {
+					name: 'amfobject',
+					meshes: []
+				};
+				let currColor = null;
+				let 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' ) {
+						let currMeshNode = currObjNode.firstElementChild;
+						const 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' ) {
+								const 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 };
-
-		}
+			const xmlData = loadDocument( data );
+			let amfName = '';
+			let amfAuthor = '';
+			const amfScale = loadDocumentScale( xmlData );
+			const amfMaterials = {};
+			const amfObjects = {};
+			const childNodes = xmlData.documentElement.childNodes;
+			let i, j;
 
-		var xmlData = loadDocument( data );
-		var amfName = '';
-		var amfAuthor = '';
-		var amfScale = loadDocumentScale( xmlData );
-		var amfMaterials = {};
-		var amfObjects = {};
-		var childNodes = xmlData.documentElement.childNodes;
+			for ( i = 0; i < childNodes.length; i ++ ) {
 
-		var i, j;
+				const child = childNodes[ i ];
 
-		for ( i = 0; i < childNodes.length; i ++ ) {
+				if ( child.nodeName === 'metadata' ) {
 
-			var child = childNodes[ i ];
+					if ( child.attributes.type !== undefined ) {
 
-			if ( child.nodeName === 'metadata' ) {
+						if ( child.attributes.type.value === 'name' ) {
 
-				if ( child.attributes.type !== undefined ) {
+							amfName = child.textContent;
 
-					if ( child.attributes.type.value === 'name' ) {
+						} else if ( child.attributes.type.value === 'author' ) {
 
-						amfName = child.textContent;
+							amfAuthor = 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 );
+					const loadedMaterial = loadMaterials( child );
+					amfMaterials[ loadedMaterial.id ] = loadedMaterial.material;
 
-				amfMaterials[ loadedMaterial.id ] = loadedMaterial.material;
+				} else if ( child.nodeName === 'object' ) {
 
-			} else if ( child.nodeName === 'object' ) {
+					const loadedObject = loadObject( child );
+					amfObjects[ loadedObject.id ] = loadedObject.obj;
 
-				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';
+			const sceneObject = new THREE.Group();
+			const 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 ( const id in amfObjects ) {
 
-			var part = amfObjects[ id ];
-			var meshes = part.meshes;
-			var newObject = new THREE.Group();
-			newObject.name = part.name || '';
+				const part = amfObjects[ id ];
+				const meshes = part.meshes;
+				const 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;
+					let objDefaultMaterial = defaultMaterial;
+					const mesh = meshes[ i ];
+					const vertices = new THREE.Float32BufferAttribute( mesh.vertices, 3 );
+					let 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;
+						const 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;
+						}
 
 					}
 
-				}
+					const volumes = mesh.volumes;
 
-				var volumes = mesh.volumes;
+					for ( j = 0; j < volumes.length; j ++ ) {
 
-				for ( j = 0; j < volumes.length; j ++ ) {
+						const volume = volumes[ j ];
+						const newGeometry = new THREE.BufferGeometry();
+						let 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;
+
+} )();

+ 237 - 268
examples/js/loaders/BVHLoader.js

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

+ 463 - 491
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,162 @@
  * 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;
+	const _taskCache = new WeakMap();
 
-		return this;
+	class BasisTextureLoader extends THREE.Loader {
 
-	},
+		constructor( manager ) {
 
-	setWorkerLimit: function ( workerLimit ) {
+			super( manager );
+			this.transcoderPath = '';
+			this.transcoderBinary = null;
+			this.transcoderPending = null;
+			this.workerLimit = 4;
+			this.workerPool = [];
+			this.workerNextTaskID = 1;
+			this.workerSourceURL = '';
+			this.workerConfig = null;
 
-		this.workerLimit = workerLimit;
+		}
 
-		return this;
+		setTranscoderPath( path ) {
 
-	},
+			this.transcoderPath = path;
+			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' )
-		};
+		setWorkerLimit( workerLimit ) {
 
-		return this;
+			this.workerLimit = workerLimit;
+			return this;
 
-	},
+		}
 
-	load: function ( url, onLoad, onProgress, onError ) {
+		detectSupport( renderer ) {
 
-		var loader = new THREE.FileLoader( this.manager );
+			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.setResponseType( 'arraybuffer' );
-		loader.setWithCredentials( this.withCredentials );
+		}
 
-		var texture = new THREE.CompressedTexture();
+		load( url, onLoad, onProgress, onError ) {
 
-		loader.load( url, ( buffer ) => {
+			const loader = new THREE.FileLoader( this.manager );
+			loader.setResponseType( 'arraybuffer' );
+			loader.setWithCredentials( this.withCredentials );
+			const texture = new THREE.CompressedTexture();
+			loader.load( url, buffer => {
 
-			// Check for an existing task using this buffer. A transferred buffer cannot be transferred
-			// again from this thread.
-			if ( THREE.BasisTextureLoader.taskCache.has( buffer ) ) {
+				// Check for an existing task using this buffer. A transferred buffer cannot be transferred
+				// again from this thread.
+				if ( _taskCache.has( buffer ) ) {
 
-				var cachedTask = THREE.BasisTextureLoader.taskCache.get( buffer );
+					const cachedTask = _taskCache.get( buffer );
 
-				return cachedTask.promise.then( onLoad ).catch( onError );
+					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 KTX2Loader. */
 
-	/** Low-level transcoding API, exposed for use by THREE.KTX2Loader. */
-	parseInternalAsync: function ( options ) {
 
-		var { levels } = options;
+		parseInternalAsync( options ) {
 
-		var buffers = new Set();
+			const {
+				levels
+			} = options;
+			const buffers = new Set();
 
-		for ( var i = 0; i < levels.length; i ++ ) {
+			for ( let 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<CompressedTexture>}
+   */
 
-	/**
-	 * @param {ArrayBuffer[]} buffers
-	 * @param {object?} config
-	 * @return {Promise<THREE.CompressedTexture>}
-	 */
-	_createTexture: function ( buffers, config ) {
 
-		var worker;
-		var taskID;
+		_createTexture( buffers, config = {} ) {
 
-		var taskConfig = config || {};
-		var taskCost = 0;
+			let worker;
+			let taskID;
+			const taskConfig = config;
+			let taskCost = 0;
 
-		for ( var i = 0; i < buffers.length; i ++ ) {
+			for ( let i = 0; i < buffers.length; i ++ ) {
 
-			taskCost += buffers[ i ].byteLength;
+				taskCost += buffers[ i ].byteLength;
 
-		}
+			}
 
-		var texturePending = this._allocateWorker( taskCost )
-			.then( ( _worker ) => {
+			const 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 texture = new THREE.CompressedTexture( mipmaps, width, height, format, THREE.UnsignedByteType );
+				const {
+					mipmaps,
+					width,
+					height,
+					format
+				} = message;
+				const 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 +176,560 @@ THREE.BasisTextureLoader.prototype = Object.assign( Object.create( THREE.Loader.
 
 				}
 
-			} );
-
-		// Cache the task result.
-		THREE.BasisTextureLoader.taskCache.set( buffers[ 0 ], { promise: texturePending } );
+			} ); // Cache the task result.
 
-		return texturePending;
+			_taskCache.set( buffers[ 0 ], {
+				promise: texturePending
+			} );
 
-	},
+			return texturePending;
 
-	_initTranscoder: function () {
+		}
 
-		if ( ! this.transcoderPending ) {
+		_initTranscoder() {
 
-			// Load transcoder wrapper.
-			var jsLoader = new THREE.FileLoader( this.manager );
-			jsLoader.setPath( this.transcoderPath );
-			jsLoader.setWithCredentials( this.withCredentials );
-			var jsContent = new Promise( ( resolve, reject ) => {
+			if ( ! this.transcoderPending ) {
 
-				jsLoader.load( 'basis_transcoder.js', resolve, undefined, reject );
+				// Load transcoder wrapper.
+				const jsLoader = new THREE.FileLoader( this.manager );
+				jsLoader.setPath( this.transcoderPath );
+				jsLoader.setWithCredentials( this.withCredentials );
+				const jsContent = new Promise( ( resolve, reject ) => {
 
-			} );
+					jsLoader.load( 'basis_transcoder.js', resolve, undefined, reject );
 
-			// 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 WASM binary.
 
-				binaryLoader.load( 'basis_transcoder.wasm', resolve, undefined, reject );
-
-			} );
+				const binaryLoader = new THREE.FileLoader( this.manager );
+				binaryLoader.setPath( this.transcoderPath );
+				binaryLoader.setResponseType( 'arraybuffer' );
+				binaryLoader.setWithCredentials( this.withCredentials );
+				const binaryContent = new Promise( ( resolve, reject ) => {
 
-			this.transcoderPending = Promise.all( [ jsContent, binaryContent ] )
-				.then( ( [ jsContent, binaryContent ] ) => {
+					binaryLoader.load( 'basis_transcoder.wasm', resolve, undefined, reject );
 
-					var fn = THREE.BasisTextureLoader.BasisWorker.toString();
-
-					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 ] ) => {
 
+					const fn = BasisTextureLoader.BasisWorker.toString();
+					const body = [ '/* constants */', 'let _EngineFormat = ' + JSON.stringify( BasisTextureLoader.EngineFormat ), 'let _TranscoderFormat = ' + JSON.stringify( BasisTextureLoader.TranscoderFormat ), 'let _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;
+			return this.transcoderPending;
 
-	},
+		}
 
-	_allocateWorker: function ( taskCost ) {
+		_allocateWorker( taskCost ) {
 
-		return this._initTranscoder().then( () => {
+			return this._initTranscoder().then( () => {
 
-			if ( this.workerPool.length < this.workerLimit ) {
+				if ( this.workerPool.length < this.workerLimit ) {
 
-				var worker = new Worker( this.workerSourceURL );
+					const worker = new Worker( this.workerSourceURL );
+					worker._callbacks = {};
+					worker._taskLoad = 0;
+					worker.postMessage( {
+						type: 'init',
+						config: this.workerConfig,
+						transcoderBinary: this.transcoderBinary
+					} );
 
-				worker._callbacks = {};
-				worker._taskLoad = 0;
+					worker.onmessage = function ( e ) {
 
-				worker.postMessage( {
-					type: 'init',
-					config: this.workerConfig,
-					transcoderBinary: this.transcoderBinary,
-				} );
+						const message = e.data;
 
-				worker.onmessage = function ( e ) {
+						switch ( message.type ) {
 
-					var message = e.data;
+							case 'transcode':
+								worker._callbacks[ message.id ].resolve( message );
 
-					switch ( message.type ) {
+								break;
 
-						case 'transcode':
-							worker._callbacks[ message.id ].resolve( message );
-							break;
+							case 'error':
+								worker._callbacks[ message.id ].reject( message );
 
-						case 'error':
-							worker._callbacks[ message.id ].reject( message );
-							break;
+								break;
 
-						default:
-							console.error( 'THREE.BasisTextureLoader: Unexpected message, "' + message.type + '"' );
+							default:
+								console.error( 'THREE.BasisTextureLoader: Unexpected message, "' + message.type + '"' );
 
-					}
+						}
 
-				};
+					};
 
-				this.workerPool.push( worker );
+					this.workerPool.push( worker );
 
-			} else {
+				} else {
 
-				this.workerPool.sort( function ( a, b ) {
+					this.workerPool.sort( function ( a, b ) {
 
-					return a._taskLoad > b._taskLoad ? - 1 : 1;
+						return a._taskLoad > b._taskLoad ? - 1 : 1;
 
-				} );
+					} );
 
-			}
+				}
 
-			var worker = this.workerPool[ this.workerPool.length - 1 ];
+				const worker = this.workerPool[ this.workerPool.length - 1 ];
+				worker._taskLoad += taskCost;
+				return worker;
 
-			worker._taskLoad += taskCost;
+			} );
 
-			return worker;
+		}
 
-		} );
+		dispose() {
 
-	},
+			for ( let 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;
 
 		}
 
-		this.workerPool.length = 0;
-
-		return this;
-
 	}
+	/* CONSTANTS */
 
-} );
-
-/* 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,
-};
+		let config;
+		let transcoderPending;
+		let BasisModule;
+		const EngineFormat = _EngineFormat; // eslint-disable-line no-undef
 
+		const TranscoderFormat = _TranscoderFormat; // eslint-disable-line no-undef
 
-/* WEB WORKER */
+		const BasisFormat = _BasisFormat; // eslint-disable-line no-undef
 
-THREE.BasisTextureLoader.BasisWorker = function () {
+		onmessage = function ( e ) {
 
-	var config;
-	var transcoderPending;
-	var BasisModule;
+			const 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;
+							const {
+								width,
+								height,
+								hasAlpha,
+								mipmaps,
+								format
+							} = message.taskConfig.lowLevel ? transcodeLowLevel( message.taskConfig ) : transcode( message.buffers[ 0 ] );
+							const buffers = [];
 
-			case 'transcode':
-				transcoderPending.then( () => {
+							for ( let 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;
-
-		}
-
-	};
-
-	function init( wasmBinary ) {
-
-		transcoderPending = new Promise( ( resolve ) => {
-
-			BasisModule = { wasmBinary, onRuntimeInitialized: resolve };
-			BASIS( BasisModule ); // eslint-disable-line no-undef
-
-		} ).then( () => {
-
-			BasisModule.initializeBasis();
+					} );
+					break;
 
-		} );
-
-	}
-
-	function transcodeLowLevel( taskConfig ) {
-
-		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 = [];
+		function init( wasmBinary ) {
 
-		if ( basisFormat === BasisFormat.ETC1S ) {
+			transcoderPending = new Promise( resolve => {
 
-			var transcoder = new BasisModule.LowLevelETC1SImageTranscoder();
+				BasisModule = {
+					wasmBinary,
+					onRuntimeInitialized: resolve
+				};
+				BASIS( BasisModule ); // eslint-disable-line no-undef
 
-			var { endpointCount, endpointsData, selectorCount, selectorsData, tablesData } = taskConfig.globalData;
+			} ).then( () => {
 
-			try {
+				BasisModule.initializeBasis();
 
-				var ok;
+			} );
 
-				ok = transcoder.decodePalettes( endpointCount, endpointsData, selectorCount, selectorsData );
+		}
 
-				assert( ok, 'THREE.BasisTextureLoader: decodePalettes() failed.' );
+		function transcodeLowLevel( taskConfig ) {
 
-				ok = transcoder.decodeTables( tablesData );
+			const {
+				basisFormat,
+				width,
+				height,
+				hasAlpha
+			} = taskConfig;
+			const {
+				transcoderFormat,
+				engineFormat
+			} = getTranscoderFormat( basisFormat, width, height, hasAlpha );
+			const blockByteLength = BasisModule.getBytesPerBlockOrPixel( transcoderFormat );
+			assert( BasisModule.isFormatSupported( transcoderFormat ), 'THREE.BasisTextureLoader: Unsupported format.' );
+			const mipmaps = [];
+
+			if ( basisFormat === BasisFormat.ETC1S ) {
+
+				const transcoder = new BasisModule.LowLevelETC1SImageTranscoder();
+				const {
+					endpointCount,
+					endpointsData,
+					selectorCount,
+					selectorsData,
+					tablesData
+				} = taskConfig.globalData;
+
+				try {
+
+					let 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 ( let i = 0; i < taskConfig.levels.length; i ++ ) {
+
+						const level = taskConfig.levels[ i ];
+						const imageDesc = taskConfig.globalData.imageDescs[ i ];
+						const dstByteLength = getTranscodedImageByteLength( transcoderFormat, level.width, level.height );
+						const 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
+						} );
 
-				assert( ok, 'THREE.BasisTextureLoader: decodeTables() failed.' );
+					}
 
-				for ( var i = 0; i < taskConfig.levels.length; i ++ ) {
+				} finally {
 
-					var level = taskConfig.levels[ i ];
-					var imageDesc = taskConfig.globalData.imageDescs[ i ];
+					transcoder.delete();
 
-					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
-					);
+			} else {
 
-					assert( ok, 'THREE.BasisTextureLoader: transcodeImage() failed for level ' + level.index + '.' );
+				for ( let i = 0; i < taskConfig.levels.length; i ++ ) {
 
-					mipmaps.push( { data: dst, width: level.width, height: level.height } );
+					const level = taskConfig.levels[ i ];
+					const dstByteLength = getTranscodedImageByteLength( transcoderFormat, level.width, level.height );
+					const dst = new Uint8Array( dstByteLength );
+					const 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 ) {
+			const basisFile = new BasisModule.BasisFile( new Uint8Array( buffer ) );
+			const basisFormat = basisFile.isUASTC() ? BasisFormat.UASTC_4x4 : BasisFormat.ETC1S;
+			const width = basisFile.getImageWidth( 0, 0 );
+			const height = basisFile.getImageHeight( 0, 0 );
+			const levels = basisFile.getNumLevels( 0 );
+			const 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();
+			}
 
-		}
+			const {
+				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' );
+			}
 
-		}
+			const mipmaps = [];
 
-		var mipmaps = [];
+			for ( let mip = 0; mip < levels; mip ++ ) {
 
-		for ( var mip = 0; mip < levels; mip ++ ) {
+				const mipWidth = basisFile.getImageWidth( 0, mip );
+				const mipHeight = basisFile.getImageHeight( 0, mip );
+				const dst = new Uint8Array( basisFile.getImageTranscodedSizeInBytes( 0, mip, transcoderFormat ) );
+				const 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.
+
+
+		const 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
+		} ];
+		const 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;
+		} );
+		const 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 ) {
+			let transcoderFormat;
+			let engineFormat;
+			const options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS;
 
-		var transcoderFormat;
-		var engineFormat;
+			for ( let i = 0; i < options.length; i ++ ) {
 
-		var options = basisFormat === BasisFormat.ETC1S ? ETC1S_OPTIONS : UASTC_OPTIONS;
+				const 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;
+		function getWidthInBlocks( transcoderFormat, width ) {
 
-		return { transcoderFormat, engineFormat };
+			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 ) {
 
-	}
+			const 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
+				const paddedWidth = width + 3 & ~ 3;
+				const 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;
 
-};
+} )();

Энэ ялгаанд хэт олон файл өөрчлөгдсөн тул зарим файлыг харуулаагүй болно