Mr.doob 4 年之前
父节点
当前提交
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>
 		<h2>Methods</h2>
 		<p>See the base [page:LineSegments] class for common methods.</p>
 		<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>
 		<h2>Source</h2>
 
 
 		<p>
 		<p>

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

@@ -68,6 +68,10 @@ scene.add( helper );
 		<h2>Methods</h2>
 		<h2>Methods</h2>
 		<p>See the base [page:LineSegments] class for common methods.</p>
 		<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>
 		<h3>[method:null update]()</h3>
 		<p>Updates the helper based on the projectionMatrix of the camera.</p>
 		<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>
 		<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>
 		<h3>[method:DirectionalLight copy]( [param:DirectionalLight source] )</h3>
 		<p>
 		<p>
 		Copies value of all the properties from the [page:DirectionalLight source] to this
 		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.
 			See the base [page:Object3D Object3D] class for common methods.
 		</p>
 		</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>
 		<h3>[method:Light copy]( [param:Light source] )</h3>
 		<p>
 		<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.
 			See the base [page:Light Light] class for common methods.
 		</p>
 		</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>
 		<h3>[method:PointLight copy]( [param:PointLight source] )</h3>
 		<p>
 		<p>
 		Copies value of all the properties from the [page:PointLight source] to this
 		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>
 		<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>
 		<h3>[method:SpotLight copy]( [param:SpotLight source] )</h3>
 		<p>
 		<p>
 		Copies value of all the properties from the [page:SpotLight source] to this
 		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.
 		Used internally by the renderer to get the number of viewports that need to be rendered for this shadow.
 		</p>
 		</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>
 		<h3>[method:LightShadow copy]( [param:LightShadow source] )</h3>
 		<p>
 		<p>
 		Copies value of all the properties from the [page:LightShadow source] to this
 		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,
 		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 />
 		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>
 		</p>
 
 
 		<h3>[method:Color setColorName]( [param:String style] ) </h3>
 		<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].
 		Updates the [page:Matrix4 MatrixWorld].
 		</p>
 		</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>
 		<h2>Source</h2>
 
 
 		<p>
 		<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>
 		<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>
 		<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>
 		<h3>[method:null dispose]( )</h3>
 		<p>Dispose of the current rendering context.</p>
 		<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)" 也能支持,
 		半透明颜色例如 "rgba(255, 0, 0, 0.5)" and "hsla(0, 100%, 50%, 0.5)" 也能支持,
 		但是alpha通道的值将会被丢弃。<br /><br />
 		但是alpha通道的值将会被丢弃。<br /><br />
 
 
-		注意,对于X11颜色名称,多个单词(如暗橙色)变成字符串“darkorange”(全部是小写字母)
+		注意,对于X11颜色名称,多个单词(如暗橙色)变成字符串“darkorange”。
 		</p>
 		</p>
 
 
 		<h3>[method:Color setColorName]( [param:String style] ) </h3>
 		<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>
 		<h3>[property:Number panSpeed]</h3>
 		<p>
 		<p>
-			The zoom speed. Default is *0.3*.
+			The pan speed. Default is *0.3*.
 		</p>
 		</p>
 
 
 		<h3>[property:Number rotateSpeed]</h3>
 		<h3>[property:Number rotateSpeed]</h3>

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

@@ -52,6 +52,7 @@
 			<div>
 			<div>
 				<p>Some code editors have plugins which will spawn a simple server on demand.</p>
 				<p>Some code editors have plugins which will spawn a simple server on demand.</p>
 				<ul>
 				<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://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>
 					<li>[link:https://atom.io/packages/atom-live-server Live Server] for Atom.</li>
 				</ul>
 				</ul>
@@ -64,6 +65,24 @@
 				</p>
 				</p>
 			</div>
 			</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>
 			<h3>Node.js http-server</h3>
 			<div>
 			<div>
 				<p>Node.js has a simple HTTP server package. To install:</p>
 				<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(),
 		transformModeChanged: new Signal(),
 		snapChanged: new Signal(),
 		snapChanged: new Signal(),
 		spaceChanged: new Signal(),
 		spaceChanged: new Signal(),
-		rendererChanged: new Signal(),
+		rendererCreated: new Signal(),
 		rendererUpdated: new Signal(),
 		rendererUpdated: new Signal(),
 
 
 		sceneBackgroundChanged: new Signal(),
 		sceneBackgroundChanged: new Signal(),
@@ -414,7 +414,7 @@ Editor.prototype = {
 
 
 				} else if ( object.isSpotLight ) {
 				} else if ( object.isSpotLight ) {
 
 
-					helper = new THREE.SpotLightHelper( object, 1 );
+					helper = new THREE.SpotLightHelper( object );
 
 
 				} else if ( object.isHemisphereLight ) {
 				} 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 { 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 ) {
 function Loader( editor ) {
 
 

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

@@ -1,6 +1,6 @@
 import * as THREE from '../../build/three.module.js';
 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';
 import { UIPanel, UIRow, UIHorizontalRule } from './libs/ui.js';
 
 

+ 1 - 1
editor/js/Script.js

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

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

@@ -116,7 +116,8 @@ function SidebarProjectRenderer( editor ) {
 		currentRenderer.toneMapping = parseFloat( toneMappingSelect.getValue() );
 		currentRenderer.toneMapping = parseFloat( toneMappingSelect.getValue() );
 		currentRenderer.toneMappingExposure = toneMappingExposure.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 ) {
 function SidebarProject( editor ) {
 
 
 	var config = editor.config;
 	var config = editor.config;
+	var signals = editor.signals;
 	var strings = editor.strings;
 	var strings = editor.strings;
 
 
 	var container = new UISpan();
 	var container = new UISpan();
@@ -69,6 +70,15 @@ function SidebarProject( editor ) {
 
 
 	}
 	}
 
 
+	// Signals
+
+	signals.editorCleared.add( function () {
+
+		title.setValue( '' );
+		config.setKey( 'project/title', '' );
+
+	} );
+
 	return container;
 	return container;
 
 
 }
 }

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

@@ -1,6 +1,7 @@
 import * as THREE from '../../build/three.module.js';
 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';
 import { XRControllerModelFactory } from '../../examples/jsm/webxr/XRControllerModelFactory.js';
 
 
@@ -29,31 +30,29 @@ class VR {
 
 
 			if ( group === null ) {
 			if ( group === null ) {
 
 
-				group = new THREE.Group();
+				group = new InteractiveGroup( renderer );
 				editor.sceneHelpers.add( group );
 				editor.sceneHelpers.add( group );
 
 
 				const mesh = new HTMLMesh( sidebar );
 				const mesh = new HTMLMesh( sidebar );
 				mesh.position.set( 1, 1.5, - 0.5 );
 				mesh.position.set( 1, 1.5, - 0.5 );
 				mesh.rotation.y = - 0.5;
 				mesh.rotation.y = - 0.5;
+				mesh.scale.setScalar( 2 );
 				group.add( mesh );
 				group.add( mesh );
 
 
 				intersectables.push( mesh );
 				intersectables.push( mesh );
 
 
 				// controllers
 				// 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();
 				const geometry = new THREE.BufferGeometry();
 				geometry.setFromPoints( [ new THREE.Vector3( 0, 0, 0 ), new THREE.Vector3( 0, 0, - 5 ) ] );
 				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 ) );
 				controller1.add( new THREE.Line( geometry ) );
+				group.add( controller1 );
+
+				const controller2 = renderer.xr.getController( 1 );
 				controller2.add( new THREE.Line( geometry ) );
 				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
 
 
 		signals.toggleVR.add( () => {
 		signals.toggleVR.add( () => {
@@ -153,7 +117,7 @@ class VR {
 
 
 		} );
 		} );
 
 
-		signals.rendererChanged.add( ( value ) => {
+		signals.rendererCreated.add( ( value ) => {
 
 
 			renderer = value;
 			renderer = value;
 			renderer.xr.enabled = true;
 			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';
 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 };
 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 ) {
 		if ( renderer !== null ) {
 
 

+ 6 - 2
editor/sw.js

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

+ 2 - 1
examples/files.json

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

+ 1 - 1
examples/index.html

@@ -51,7 +51,7 @@
 
 
 		</div>
 		</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>
 		<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
  * CCD Algorithm
  *  - https://sites.google.com/site/auraliusproject/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 ] );
 				this.updateOne( iks[ i ] );
 
 
@@ -54,188 +75,161 @@ THREE.CCDIKSolver = ( function () {
 
 
 			return this;
 			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;
 				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 ];
 					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 ) {
 			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 ) );
 				geometry.setAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
-
 				return geometry;
 				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( createTargetMesh() );
 				this.add( createEffectorMesh() );
 				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() );
 					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 ) {
 	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;
 				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;
 						if ( scope.enableRotate === false ) return;
-
-						handleTouchStartRotate( event );
-
-						state = STATE.TOUCH_ROTATE;
-
+						handleTouchMoveRotate( event );
+						scope.update();
 						break;
 						break;
 
 
-					case THREE.TOUCH.PAN:
-
+					case STATE.TOUCH_PAN:
 						if ( scope.enablePan === false ) return;
 						if ( scope.enablePan === false ) return;
-
-						handleTouchStartPan( event );
-
-						state = STATE.TOUCH_PAN;
-
+						handleTouchMovePan( event );
+						scope.update();
 						break;
 						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;
 						if ( scope.enableZoom === false && scope.enablePan === false ) return;
-
-						handleTouchStartDollyPan( event );
-
-						state = STATE.TOUCH_DOLLY_PAN;
-
+						handleTouchMoveDollyPan( event );
+						scope.update();
 						break;
 						break;
 
 
-					case THREE.TOUCH.DOLLY_ROTATE:
-
+					case STATE.TOUCH_DOLLY_ROTATE:
 						if ( scope.enableZoom === false && scope.enableRotate === false ) return;
 						if ( scope.enableZoom === false && scope.enableRotate === false ) return;
-
-						handleTouchStartDollyRotate( event );
-
-						state = STATE.TOUCH_DOLLY_ROTATE;
-
+						handleTouchMoveDollyRotate( event );
+						scope.update();
 						break;
 						break;
 
 
 					default:
 					default:
-
 						state = STATE.NONE;
 						state = STATE.NONE;
 
 
 				}
 				}
 
 
-				break;
+			}
 
 
-			default:
+			function onTouchEnd( event ) {
 
 
+				if ( scope.enabled === false ) return;
+				handleTouchEnd( event );
+				scope.dispatchEvent( _endEvent );
 				state = STATE.NONE;
 				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 );
 				lastPosition.copy( scope.object.position );
 				lastZoom = scope.object.zoom;
 				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 ) {
 					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 ) {
 					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 ) {
 					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 ) {
 	function getPointer( event ) {
 
 
-		if ( scope.domElement.ownerDocument.pointerLockElement ) {
+		if ( this.domElement.ownerDocument.pointerLockElement ) {
 
 
 			return {
 			return {
 				x: 0,
 				x: 0,
@@ -591,10 +617,8 @@ THREE.TransformControls = function ( camera, domElement ) {
 
 
 		} else {
 		} 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 {
 			return {
 				x: ( pointer.clientX - rect.left ) / rect.width * 2 - 1,
 				x: ( pointer.clientX - rect.left ) / rect.width * 2 - 1,
 				y: - ( pointer.clientY - rect.top ) / rect.height * 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 ) {
 	function onPointerHover( event ) {
 
 
-		if ( ! scope.enabled ) return;
+		if ( ! this.enabled ) return;
 
 
 		switch ( event.pointerType ) {
 		switch ( event.pointerType ) {
 
 
 			case 'mouse':
 			case 'mouse':
 			case 'pen':
 			case 'pen':
-				scope.pointerHover( getPointer( event ) );
+				this.pointerHover( this._getPointer( event ) );
 				break;
 				break;
 
 
 		}
 		}
@@ -624,1040 +646,826 @@ THREE.TransformControls = function ( camera, domElement ) {
 
 
 	function onPointerDown( event ) {
 	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 ) {
 	function onPointerMove( event ) {
 
 
-		if ( ! scope.enabled ) return;
-
-		scope.pointerMove( getPointer( event ) );
+		if ( ! this.enabled ) return;
+		this.pointerMove( this._getPointer( event ) );
 
 
 	}
 	}
 
 
 	function onPointerUp( 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;
 							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;
 							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;
 							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;
 							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
  * A bunch of parametric curves
  *
  *
  * Formulas collected from various sources
  * Formulas collected from various sources
@@ -9,402 +11,328 @@
  * http://www.mi.sanu.ac.rs/vismath/taylorapril2011/Taylor.pdf
  * http://www.mi.sanu.ac.rs/vismath/taylorapril2011/Taylor.pdf
  * https://prideout.net/blog/old/blog/index.html@p=44.html
  * https://prideout.net/blog/old/blog/index.html@p=44.html
  */
  */
-
-THREE.Curves = ( function () {
-
 	// GrannyKnot
 	// 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;
 		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,
 		GrannyKnot: GrannyKnot,
 		HeartCurve: HeartCurve,
 		HeartCurve: HeartCurve,
 		VivianiCurve: VivianiCurve,
 		VivianiCurve: VivianiCurve,
@@ -421,4 +349,6 @@ THREE.Curves = ( function () {
 		DecoratedTorusKnot5c: DecoratedTorusKnot5c
 		DecoratedTorusKnot5c: DecoratedTorusKnot5c
 	};
 	};
 
 
+	THREE.Curves = Curves;
+
 } )();
 } )();

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

@@ -1,66 +1,75 @@
-/**
+( function () {
+
+	/**
  * NURBS curve object
  * 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.
  * 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
  * NURBS surface object
  *
  *
  * Implementation is based on (x, y [, z=0 [, w=1]]) control points with w=weight.
  * 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
  * NURBS utils
  *
  *
  * See NURBSCurve and NURBSSurface.
  * See NURBSCurve and NURBSSurface.
  **/
  **/
 
 
-
-/**************************************************************
+	/**************************************************************
  *	NURBS Utils
  *	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/
  * 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
  * Maybe more about this later with a blog post at http://lab4games.net/zz85/blog
  *
  *
  * 16 April 2012 - @blurspline
  * 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
  * Reference: https://en.wikipedia.org/wiki/Cel_shading
  *
  *
  * API
  * API
  *
  *
  * 1. Traditional
  * 1. Traditional
  *
  *
- * var effect = new THREE.OutlineEffect( renderer );
+ * const effect = new OutlineEffect( renderer );
  *
  *
  * function render() {
  * function render() {
  *
  *
@@ -15,8 +17,8 @@
  *
  *
  * 2. VR compatible
  * 2. VR compatible
  *
  *
- * var effect = new THREE.OutlineEffect( renderer );
- * var renderingOutline = false;
+ * const effect = new OutlineEffect( renderer );
+ * let renderingOutline = false;
  *
  *
  * scene.onAfterRender = function () {
  * scene.onAfterRender = function () {
  *
  *
@@ -37,7 +39,7 @@
  * }
  * }
  *
  *
  * // How to set default outline parameters
  * // How to set default outline parameters
- * new THREE.OutlineEffect( renderer, {
+ * new OutlineEffect( renderer, {
  * 	defaultThickness: 0.01,
  * 	defaultThickness: 0.01,
  * 	defaultColor: [ 0, 0, 0 ],
  * 	defaultColor: [ 0, 0, 0 ],
  * 	defaultAlpha: 0.8,
  * 	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
  * 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
  * https://github.com/gkjohnson/collada-exporter-js
  *
  *
  * Usage:
  * Usage:
- *  var exporter = new THREE.ColladaExporter();
+ *  const exporter = new ColladaExporter();
  *
  *
- *  var data = exporter.parse(mesh);
+ *  const data = exporter.parse(mesh);
  *
  *
  * Format Definition:
  * Format Definition:
  *  https://www.khronos.org/collada/
  *  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 ) ) {
 					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 ) ) {
 					if ( ! HAS_TEXT.test( tag ) && ! IS_SELF_CLOSING.test( tag ) && ! IS_END_TAG.test( tag ) ) {
 
 
@@ -72,592 +65,403 @@ THREE.ColladaExporter.prototype = {
 
 
 					return res;
 					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.
  * Export draco compressed files from threejs geometry objects.
  *
  *
  * Draco files are compressed and usually are smaller than conventional 3D file formats.
  * Draco files are compressed and usually are smaller than conventional 3D file formats.
@@ -12,237 +14,211 @@
  *  - exportNormals
  *  - 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
  * Dependencies
  *  - mmd-parser https://github.com/takahirox/mmd-parser
  *  - mmd-parser https://github.com/takahirox/mmd-parser
  */
  */
 
 
-THREE.MMDExporter = function () {
-
-	// Unicode to Shift_JIS table
-	var u2sTable;
-
-	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 {
 			} 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
  * https://github.com/gkjohnson/ply-exporter-js
  *
  *
  * Usage:
  * Usage:
- *  var exporter = new THREE.PLYExporter();
+ *  const exporter = new PLYExporter();
  *
  *
  *  // second argument is a list of options
  *  // second argument is a list of options
  *  exporter.parse(mesh, data => console.log(data), { binary: true, excludeAttributes: [ 'color' ], littleEndian: true });
  *  exporter.parse(mesh, data => console.log(data), { binary: true, excludeAttributes: [ 'color' ], littleEndian: true });
@@ -11,513 +13,427 @@
  * http://paulbourke.net/dataformats/ply/
  * 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 {
 						} 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 {
 						} 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:
  * Usage:
- *  var exporter = new THREE.STLExporter();
+ *  const exporter = new STLExporter();
  *
  *
  *  // second argument is a list of options
  *  // 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.
  * 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.
  * 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.
  * @fileoverview LightningStrike object for creating lightning strikes and voltaic arcs.
  *
  *
  *
  *
  * Usage
  * Usage
  *
  *
- * var myRay = new THREE.LightningStrike( paramsObject );
+ * var myRay = new LightningStrike( paramsObject );
  * var myRayMesh = new THREE.Mesh( myRay, myMaterial );
  * var myRayMesh = new THREE.Mesh( myRay, myMaterial );
  * scene.add( myRayMesh );
  * 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 {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.
  * 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.
  * 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.
  * 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
  * 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 );
 			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
  * 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
   * 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
   * 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
   * 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
  * 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 = {
  * parameters = {
  *  color: <hex>,
  *  color: <hex>,
  *  linewidth: <float>,
  *  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 <common>
 		#include <color_pars_vertex>
 		#include <color_pars_vertex>
 		#include <fog_pars_vertex>
 		#include <fog_pars_vertex>
@@ -177,9 +184,7 @@ THREE.ShaderLib[ 'line' ] = {
 
 
 		}
 		}
 		`,
 		`,
-
-	fragmentShader:
-		`
+		fragmentShader: `
 		uniform vec3 diffuse;
 		uniform vec3 diffuse;
 		uniform float opacity;
 		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
 			// 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
 			// check if we intersect the sphere bounds
+
 			if ( geometry.boundingSphere === null ) {
 			if ( geometry.boundingSphere === null ) {
 
 
 				geometry.computeBoundingSphere();
 				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
 			// check if we intersect the box bounds
+
+
 			if ( geometry.boundingBox === null ) {
 			if ( geometry.boundingBox === null ) {
 
 
 				geometry.computeBoundingBox();
 				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
 			// 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
 			// sitting at the camera origin which will cause "w" to be 0 when
 			// applying the projection matrix.
 			// 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 ) {
 				if ( isBehindCameraNear ) {
 
 
 					continue;
 					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 ) {
 				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( {
 					intersects.push( {
-
 						point: point,
 						point: point,
 						pointOnLine: pointOnLine,
 						pointOnLine: pointOnLine,
 						distance: ray.origin.distanceTo( point ),
 						distance: ray.origin.distanceTo( point ),
-
 						object: this,
 						object: this,
 						face: null,
 						face: null,
 						faceIndex: i,
 						faceIndex: i,
 						uv: null,
 						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 ) {
 			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 ) {
 			if ( start !== undefined && end !== undefined ) {
 
 
 				this.boundingBox.setFromBufferAttribute( start );
 				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 ) {
 			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 ) {
 			if ( start !== undefined && end !== undefined ) {
 
 
-				var center = this.boundingSphere.center;
-
+				const center = this.boundingSphere.center;
 				this.boundingBox.getCenter( 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( 'instanceDistanceStart', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 0 ) ); // d0
+
 			geometry.setAttribute( 'instanceDistanceEnd', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1
 			geometry.setAttribute( 'instanceDistanceEnd', new THREE.InterleavedBufferAttribute( instanceDistanceBuffer, 1, 1 ) ); // d1
 
 
 			return this;
 			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/
  * 3D Manufacturing Format (3MF) specification: https://3mf.io/specification/
  *
  *
@@ -12,1435 +14,1323 @@
  *
  *
  * - Texture 2D
  * - Texture 2D
  * - Texture 2D Groups
  * - Texture 2D Groups
- * - Color Groups (Vertex Colors)
+ * - THREE.Color Groups (Vertex Colors)
  * - Metallic Display Properties (PBR)
  * - 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.
  * example loaders in the three.js project.
  *
  *
  * More information about the AMF format: http://amf.wikispaces.com
  * More information about the AMF format: http://amf.wikispaces.com
  *
  *
  * Usage:
  * Usage:
- *	var loader = new AMFLoader();
+ *	const loader = new AMFLoader();
  *	loader.load('/path/to/project.amf', function(objecttree) {
  *	loader.load('/path/to/project.amf', function(objecttree) {
  *		scene.add(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
  * Description: reads BVH files and outputs a single THREE.Skeleton and an THREE.AnimationClip
  *
  *
  * Currently only supports bvh files containing a single root.
  * 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
  * Basis Universal is a "supercompressed" GPU texture and texture video
  * compression system that outputs a highly compressed intermediate file format
  * compression system that outputs a highly compressed intermediate file format
@@ -10,167 +12,162 @@
  * of web workers, before transferring the transcoded compressed texture back
  * of web workers, before transferring the transcoded compressed texture back
  * to the main thread.
  * 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.copy( _texture );
 					texture.needsUpdate = true;
 					texture.needsUpdate = true;
-
 					if ( onLoad ) onLoad( texture );
 					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;
 				worker = _worker;
 				taskID = this.workerNextTaskID ++;
 				taskID = this.workerNextTaskID ++;
-
 				return new Promise( ( resolve, reject ) => {
 				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.minFilter = mipmaps.length === 1 ? THREE.LinearFilter : THREE.LinearMipmapLinearFilter;
 				texture.magFilter = THREE.LinearFilter;
 				texture.magFilter = THREE.LinearFilter;
 				texture.generateMipmaps = false;
 				texture.generateMipmaps = false;
 				texture.needsUpdate = true;
 				texture.needsUpdate = true;
-
 				return texture;
 				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 ) {
 				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.workerSourceURL = URL.createObjectURL( new Blob( [ body ] ) );
 					this.transcoderBinary = binaryContent;
 					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',
 			if: 'astcSupported',
 			basisFormat: [ BasisFormat.UASTC_4x4 ],
 			basisFormat: [ BasisFormat.UASTC_4x4 ],
 			transcoderFormat: [ TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4 ],
 			transcoderFormat: [ TranscoderFormat.ASTC_4x4, TranscoderFormat.ASTC_4x4 ],
 			engineFormat: [ EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format ],
 			engineFormat: [ EngineFormat.RGBA_ASTC_4x4_Format, EngineFormat.RGBA_ASTC_4x4_Format ],
 			priorityETC1S: Infinity,
 			priorityETC1S: Infinity,
 			priorityUASTC: 1,
 			priorityUASTC: 1,
-			needsPowerOfTwo: false,
-		},
-		{
+			needsPowerOfTwo: false
+		}, {
 			if: 'bptcSupported',
 			if: 'bptcSupported',
 			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
 			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
 			transcoderFormat: [ TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5 ],
 			transcoderFormat: [ TranscoderFormat.BC7_M5, TranscoderFormat.BC7_M5 ],
 			engineFormat: [ EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format ],
 			engineFormat: [ EngineFormat.RGBA_BPTC_Format, EngineFormat.RGBA_BPTC_Format ],
 			priorityETC1S: 3,
 			priorityETC1S: 3,
 			priorityUASTC: 2,
 			priorityUASTC: 2,
-			needsPowerOfTwo: false,
-		},
-		{
+			needsPowerOfTwo: false
+		}, {
 			if: 'dxtSupported',
 			if: 'dxtSupported',
 			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
 			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
 			transcoderFormat: [ TranscoderFormat.BC1, TranscoderFormat.BC3 ],
 			transcoderFormat: [ TranscoderFormat.BC1, TranscoderFormat.BC3 ],
 			engineFormat: [ EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format ],
 			engineFormat: [ EngineFormat.RGB_S3TC_DXT1_Format, EngineFormat.RGBA_S3TC_DXT5_Format ],
 			priorityETC1S: 4,
 			priorityETC1S: 4,
 			priorityUASTC: 5,
 			priorityUASTC: 5,
-			needsPowerOfTwo: false,
-		},
-		{
+			needsPowerOfTwo: false
+		}, {
 			if: 'etc2Supported',
 			if: 'etc2Supported',
 			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
 			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
 			transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC2 ],
 			transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC2 ],
 			engineFormat: [ EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format ],
 			engineFormat: [ EngineFormat.RGB_ETC2_Format, EngineFormat.RGBA_ETC2_EAC_Format ],
 			priorityETC1S: 1,
 			priorityETC1S: 1,
 			priorityUASTC: 3,
 			priorityUASTC: 3,
-			needsPowerOfTwo: false,
-		},
-		{
+			needsPowerOfTwo: false
+		}, {
 			if: 'etc1Supported',
 			if: 'etc1Supported',
 			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
 			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
 			transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC1 ],
 			transcoderFormat: [ TranscoderFormat.ETC1, TranscoderFormat.ETC1 ],
 			engineFormat: [ EngineFormat.RGB_ETC1_Format, EngineFormat.RGB_ETC1_Format ],
 			engineFormat: [ EngineFormat.RGB_ETC1_Format, EngineFormat.RGB_ETC1_Format ],
 			priorityETC1S: 2,
 			priorityETC1S: 2,
 			priorityUASTC: 4,
 			priorityUASTC: 4,
-			needsPowerOfTwo: false,
-		},
-		{
+			needsPowerOfTwo: false
+		}, {
 			if: 'pvrtcSupported',
 			if: 'pvrtcSupported',
 			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
 			basisFormat: [ BasisFormat.ETC1S, BasisFormat.UASTC_4x4 ],
 			transcoderFormat: [ TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA ],
 			transcoderFormat: [ TranscoderFormat.PVRTC1_4_RGB, TranscoderFormat.PVRTC1_4_RGBA ],
 			engineFormat: [ EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format ],
 			engineFormat: [ EngineFormat.RGB_PVRTC_4BPPV1_Format, EngineFormat.RGBA_PVRTC_4BPPV1_Format ],
 			priorityETC1S: 5,
 			priorityETC1S: 5,
 			priorityUASTC: 6,
 			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;
 
 
-};
+} )();

部分文件因为文件数量过多而无法显示