Browse Source

Merge pull request #13 from mrdoob/dev

Update
Temdog007 6 years ago
parent
commit
97f9e5091d
84 changed files with 11396 additions and 483 deletions
  1. 2 2
      README.md
  2. 3 3
      docs/api/en/cameras/OrthographicCamera.html
  3. 3 2
      docs/api/en/cameras/PerspectiveCamera.html
  4. 1 1
      docs/api/en/core/BufferGeometry.html
  5. 4 2
      docs/api/en/core/Geometry.html
  6. 3 2
      docs/api/en/core/Object3D.html
  7. 6 6
      docs/api/en/helpers/BoxHelper.html
  8. 3 2
      docs/api/en/lights/Light.html
  9. 2 2
      docs/api/en/materials/Material.html
  10. 22 2
      docs/api/en/math/Box3.html
  11. 3 2
      docs/api/en/scenes/Scene.html
  12. 2 2
      docs/api/en/textures/Texture.html
  13. 20 0
      docs/manual/en/introduction/Import-via-modules.html
  14. 2 0
      editor/index.html
  15. 145 0
      editor/js/Sidebar.Geometry.ExtrudeGeometry.js
  16. 55 0
      editor/js/Sidebar.Geometry.ShapeGeometry.js
  17. 13 0
      editor/js/Strings.js
  18. 1 0
      examples/files.js
  19. 0 5
      examples/index.html
  20. 252 150
      examples/js/controls/TransformControls.js
  21. 24 8
      examples/js/loaders/3MFLoader.js
  22. 53 0
      examples/js/loaders/ColladaLoader.js
  23. 433 157
      examples/js/loaders/LDrawLoader.js
  24. 3 1
      examples/js/loaders/MTLLoader.js
  25. 169 82
      examples/js/loaders/SVGLoader.js
  26. 1 1
      examples/js/vr/WebVR.js
  27. 24 0
      examples/jsm/controls/DeviceOrientationControls.d.ts
  28. 120 0
      examples/jsm/controls/DeviceOrientationControls.js
  29. 20 0
      examples/jsm/controls/DragControls.d.ts
  30. 298 0
      examples/jsm/controls/DragControls.js
  31. 26 0
      examples/jsm/controls/EditorControls.d.ts
  32. 327 0
      examples/jsm/controls/EditorControls.js
  33. 32 0
      examples/jsm/controls/FirstPersonControls.d.ts
  34. 350 0
      examples/jsm/controls/FirstPersonControls.js
  35. 19 0
      examples/jsm/controls/FlyControls.d.ts
  36. 290 0
      examples/jsm/controls/FlyControls.js
  37. 30 0
      examples/jsm/controls/OrthographicTrackballControls.d.ts
  38. 635 0
      examples/jsm/controls/OrthographicTrackballControls.js
  39. 25 0
      examples/jsm/controls/PointerLockControls.d.ts
  40. 134 0
      examples/jsm/controls/PointerLockControls.js
  41. 1 1
      examples/jsm/controls/TrackballControls.js
  42. 46 0
      examples/jsm/controls/TransformControls.d.ts
  43. 1603 0
      examples/jsm/controls/TransformControls.js
  44. 6 1
      examples/jsm/exporters/GLTFExporter.js
  45. 24 0
      examples/jsm/loaders/BVHLoader.d.ts
  46. 428 0
      examples/jsm/loaders/BVHLoader.js
  47. 1 1
      examples/jsm/loaders/GLTFLoader.d.ts
  48. 4 2
      examples/jsm/loaders/GLTFLoader.js
  49. 5 7
      examples/jsm/loaders/MTLLoader.js
  50. 17 0
      examples/jsm/loaders/PCDLoader.d.ts
  51. 321 0
      examples/jsm/loaders/PCDLoader.js
  52. 18 0
      examples/jsm/loaders/PLYLoader.d.ts
  53. 515 0
      examples/jsm/loaders/PLYLoader.js
  54. 16 0
      examples/jsm/loaders/STLLoader.d.ts
  55. 4 7
      examples/jsm/loaders/STLLoader.js
  56. 34 0
      examples/jsm/loaders/SVGLoader.d.ts
  57. 1996 0
      examples/jsm/loaders/SVGLoader.js
  58. 16 0
      examples/jsm/loaders/TGALoader.d.ts
  59. 558 0
      examples/jsm/loaders/TGALoader.js
  60. 19 0
      examples/jsm/loaders/VRMLLoader.d.ts
  61. 1360 0
      examples/jsm/loaders/VRMLLoader.js
  62. 19 0
      examples/jsm/renderers/CSS2DRenderer.d.ts
  63. 184 0
      examples/jsm/renderers/CSS2DRenderer.js
  64. 23 0
      examples/jsm/renderers/CSS3DRenderer.d.ts
  65. 340 0
      examples/jsm/renderers/CSS3DRenderer.js
  66. 3 2
      examples/jsm/utils/ShadowMapViewer.js
  67. BIN
      examples/textures/memorial.png
  68. 2 5
      examples/webgl_gpgpu_water.html
  69. 46 1
      examples/webgl_loader_ldraw.html
  70. 3 3
      examples/webgl_loader_svg.html
  71. 140 0
      examples/webgl_loader_texture_rgbm.html
  72. 1 1
      files/main.css
  73. 1 1
      src/core/Geometry.js
  74. 12 0
      src/core/InstancedBufferGeometry.js
  75. 5 0
      src/extras/core/Interpolations.d.ts
  76. 12 2
      src/geometries/SphereGeometry.js
  77. 5 3
      src/loaders/BufferGeometryLoader.js
  78. 1 0
      src/loaders/ObjectLoader.js
  79. 1 1
      src/renderers/WebGLRenderer.js
  80. 1 1
      src/renderers/webgl/WebGLAnimation.d.ts
  81. 2 2
      src/renderers/webgl/WebGLAttributes.d.ts
  82. 17 0
      src/renderers/webgl/WebGLBackground.d.ts
  83. 5 0
      src/renderers/webgl/WebGLUtils.d.ts
  84. 26 10
      utils/modularize.js

+ 2 - 2
README.md

@@ -24,13 +24,13 @@ The aim of the project is to create an easy to use, lightweight, 3D library with
 ### Usage ###
 
 Download the [minified library](http://threejs.org/build/three.min.js) and include it in your HTML, or install and import it as a [module](http://threejs.org/docs/#manual/introduction/Import-via-modules),
-Alternatively see [how to build the library yourself](https://github.com/mrdoob/three.js/wiki/Build-instructions).
+Alternatively, see [how to build the library yourself](https://github.com/mrdoob/three.js/wiki/Build-instructions).
 
 ```html
 <script src="js/three.min.js"></script>
 ```
 
-This code creates a scene, a camera, and a geometric cube, and it adds the cube to the scene. It then creates a `WebGL` renderer for the scene and camera, and it adds that viewport to the document.body element. Finally, it animates the cube within the scene for the camera.
+This code creates a scene, a camera, and a geometric cube, and it adds the cube to the scene. It then creates a `WebGL` renderer for the scene and camera, and it adds that viewport to the `document.body` element. Finally, it animates the cube within the scene for the camera.
 
 ```javascript
 var camera, scene, renderer;

+ 3 - 3
docs/api/en/cameras/OrthographicCamera.html

@@ -130,12 +130,12 @@ scene.add( camera );</code>
 		Updates the camera projection matrix. Must be called after any change of parameters.
 		</p>
 
-		<h3>[method:JSON toJSON]()</h3>
+		<h3>[method:Object toJSON](param:object meta])</h3>
 		<p>
-		Return the camera's data in JSON format.
+		meta -- object containing metadata such as textures or images in objects' descendants.<br />
+		Convert the camera to three.js [link:https://github.com/mrdoob/three.js/wiki/JSON-Object-Scene-format-4 JSON Object/Scene format].
 		</p>
 
-
 		<h2>Source</h2>
 
 		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]

+ 3 - 2
docs/api/en/cameras/PerspectiveCamera.html

@@ -190,9 +190,10 @@ camera.setViewOffset( fullWidth, fullHeight, w * 2, h * 1, w, h );
 		Updates the camera projection matrix. Must be called after any change of parameters.
 		</p>
 
-		<h3>[method:JSON toJSON]()</h3>
+		<h3>[method:Object toJSON](param:object meta])</h3>
 		<p>
-		Return camera data in JSON format.
+		meta -- object containing metadata such as textures or images in objects' descendants.<br />
+		Convert the camera to three.js [link:https://github.com/mrdoob/three.js/wiki/JSON-Object-Scene-format-4 JSON Object/Scene format].
 		</p>
 
 		<h2>Source</h2>

+ 1 - 1
docs/api/en/core/BufferGeometry.html

@@ -327,7 +327,7 @@
 		<p>Sets the attributes for this BufferGeometry from an array of points.</p>
 
 		<h3>[method:Object toJSON]()</h3>
-		<p>Returns a JSON object representation of the BufferGeometry.</p>
+		<p>Convert the buffer geometry to three.js [link:https://github.com/mrdoob/three.js/wiki/JSON-Object-Scene-format-4 JSON Object/Scene format].</p>
 
 		<h3>[method:BufferGeometry toNonIndexed]()</h3>
 		<p>Return a non-index version of an indexed BufferGeometry.</p>

+ 4 - 2
docs/api/en/core/Geometry.html

@@ -327,8 +327,10 @@
 		Use [page:Object3D.scale] for typical real-time mesh scaling.
 		</p>
 
-		<h3>[method:JSON toJSON] ( )</h3>
-		<p>Convert the geometry to JSON format.</p>
+		<h3>[method:Object toJSON] ( )</h3>
+		<p>Convert the geometry to JSON format.<br />
+		Convert the geometry to three.js [link:https://github.com/mrdoob/three.js/wiki/JSON-Object-Scene-format-4 JSON Object/Scene format].
+		</p>
 
 		<h3>[method:Geometry translate] ( [param:Float x], [param:Float y], [param:Float z] )</h3>
 		<p>

+ 3 - 2
docs/api/en/core/Object3D.html

@@ -389,9 +389,10 @@
 			Copy the given quaternion into [page:.quaternion].
 		</p>
 
-		<h3>[method:null toJSON]( [param:Quaternion q] )</h3>
+		<h3>[method:Object toJSON]( [param:object meta] )</h3>
 		<p>
-			Convert the object to JSON format.
+		meta -- object containing metadata such as materials, textures or images for the object.<br />
+		Convert the object to three.js [link:https://github.com/mrdoob/three.js/wiki/JSON-Object-Scene-format-4 JSON Object/Scene format].
 		</p>
 
 		<h3>[method:this translateOnAxis]( [param:Vector3 axis], [param:Float distance] )</h3>

+ 6 - 6
docs/api/en/helpers/BoxHelper.html

@@ -13,8 +13,8 @@
 		<h1>[name]</h1>
 
 		<p class="desc">
-			Helper object to show the world-axis-aligned bounding box around an object.
-
+			Helper object to graphically show the world-axis-aligned bounding box around an object. The actual bounding box is handled with [page:Box3], this is just a visual helper for debugging.
+			It can be automatically resized with the [page:BoxHelper.update] method when the object it's created from is transformed.
 			Note that the object must have a [page:Geometry] or [page:BufferGeometry] for this to work,
 			so it won't work with [page:Sprite Sprites].
 		</p>
@@ -28,10 +28,10 @@
 
 
 		<code>
-		var sphere = new THREE.SphereGeometry();
-		var object = new THREE.Mesh( sphere, new THREE.MeshBasicMaterial( 0xff0000 ) );
-		var box = new THREE.BoxHelper( object, 0xffff00 );
-		scene.add( box );
+			var sphere = new THREE.SphereGeometry();
+			var object = new THREE.Mesh( sphere, new THREE.MeshBasicMaterial( 0xff0000 ) );
+			var box = new THREE.BoxHelper( object, 0xffff00 );
+			scene.add( box );
 		</code>
 
 

+ 3 - 2
docs/api/en/lights/Light.html

@@ -67,9 +67,10 @@
 		[page:Light source] light into this one.
 		</p>
 
-		<h3>[method:JSON toJSON]( [param:String meta] )</h3>
+		<h3>[method:Object toJSON]( [param:object meta] )</h3>
 		<p>
-		Return Light data in JSON format.
+		meta -- object containing metadata such as materials, textures for objects.<br />
+		Convert the light to three.js [link:https://github.com/mrdoob/three.js/wiki/JSON-Object-Scene-format-4 JSON Object/Scene format].
 		</p>
 
 		<h2>Source</h2>

+ 2 - 2
docs/api/en/materials/Material.html

@@ -307,10 +307,10 @@
 		Sets the properties based on the *values*.
 		</p>
 
-		<h3>[method:null toJSON]( [param:object meta] )</h3>
+		<h3>[method:Object toJSON]( [param:object meta] )</h3>
 		<p>
 		meta -- object containing metadata such as textures or images for the material.<br />
-		Convert the material to three.js JSON format.
+		Convert the material to three.js [link:https://github.com/mrdoob/three.js/wiki/JSON-Object-Scene-format-4 JSON Object/Scene format].
 		</p>
 
 		<h2>Source</h2>

+ 22 - 2
docs/api/en/math/Box3.html

@@ -12,11 +12,31 @@
 
 		<p class="desc">
 			Represents a box or cube in 3D space. The main purpose of this is to represent
-			the [link:https://en.wikipedia.org/wiki/Minimum_bounding_box Minimum Bounding Boxes]
-			for objects.
+			the world-axis-aligned bounding boxes for objects. 
 		</p>
 
+		
+		<h2>Example</h2>
 
+		<code>
+		// Creating the object whose bounding box we want to compute
+		var sphereObject = new THREE.Mesh( 
+			new THREE.SphereGeometry(), 
+			new THREE.MeshBasicMaterial( 0xff0000 ) 
+		);
+		// Creating the actual bounding box with Box3
+		sphereObject.geometry.computeBoundingBox();
+		var box = sphereObject.geometry.boundingBox.clone();
+
+		// ...
+		
+		// In the animation loop, to keep the bounding box updated after move/rotate/scale operations
+		sphereObject.updateMatrixWorld( true );
+		box.copy( sphereObject.geometry.boundingBox ).applyMatrix4( sphereObject.matrixWorld );
+		</code>
+
+
+		
 		<h2>Constructor</h2>
 
 

+ 3 - 2
docs/api/en/scenes/Scene.html

@@ -46,9 +46,10 @@
 
 		<h2>Methods</h2>
 
-		<h3>[method:JSON toJSON]</h3>
+		<h3>[method:Object toJSON]</h3>
 		<p>
-		Return the scene data in JSON format.
+		meta -- object containing metadata such as textures or images for the scene.<br />
+		Convert the scene to three.js [link:https://github.com/mrdoob/three.js/wiki/JSON-Object-Scene-format-4 JSON Object/Scene format].
 		</p>
 
 		<h3>[method:null dispose]()</h3>

+ 2 - 2
docs/api/en/textures/Texture.html

@@ -251,10 +251,10 @@
 		Make copy of the texture. Note this is not a "deep copy", the image is shared.
 		</p>
 
-		<h3>[method:Texture toJSON]( [param:Object meta] )</h3>
+		<h3>[method:Object toJSON]( [param:Object meta] )</h3>
 		<p>
 		meta -- optional object containing metadata.<br />
-		Convert the material to three.js JSON format.
+		Convert the texture to three.js [link:https://github.com/mrdoob/three.js/wiki/JSON-Object-Scene-format-4 JSON Object/Scene format].
 		</p>
 
 		<h3>[method:null dispose]()</h3>

+ 20 - 0
docs/manual/en/introduction/Import-via-modules.html

@@ -78,9 +78,17 @@
 			<ul>
 				<li>controls
 					<ul>
+						<li>DeviceOrientationControls</li>
+						<li>DragControls</li>
+						<li>EditorControls</li>
+						<li>FirstPersonControls</li>
+						<li>FlyControls</li>
 						<li>MapControls</li>
 						<li>OrbitControls</li>
+						<li>OrthographicTrackballControls</li>
+						<li>PointerLockControls</li>
 						<li>TrackballControls</li>
+						<li>TransformControls</li>
 					</ul>
 				</li>
 				<li>exporters
@@ -96,10 +104,16 @@
 				</li>
 				<li>loaders
 					<ul>
+						<li>BVHLoader</li>
 						<li>GLTFLoader</li>
 						<li>MTLLoader</li>
 						<li>OBJLoader</li>
+						<li>PCDLoader</li>
+						<li>PLYLoader</li>
 						<li>STLLoader</li>
+						<li>SVGLoader</li>
+						<li>TGALoader</li>
+						<li>VRMLLoader</li>
 					</ul>
 				</li>
 				<li>pmrem
@@ -108,6 +122,12 @@
 						<li>PMREMGenerator</li>
 					</ul>
 				</li>
+				<li>renderers
+					<ul>
+						<li>CSS2DRenderer</li>
+						<li>CSS3DRenderer</li>
+					</ul>
+				</li>
 				<li>utils
 					<ul>
 						<li>BufferGeometryUtils</li>

+ 2 - 0
editor/index.html

@@ -126,11 +126,13 @@
 		<script src="js/Sidebar.Geometry.BoxGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.CircleGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.CylinderGeometry.js"></script>
+		<script src="js/Sidebar.Geometry.ExtrudeGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.IcosahedronGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.OctahedronGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.PlaneGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.RingGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.SphereGeometry.js"></script>
+		<script src="js/Sidebar.Geometry.ShapeGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.TetrahedronGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.TorusGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.TorusKnotGeometry.js"></script>

+ 145 - 0
editor/js/Sidebar.Geometry.ExtrudeGeometry.js

@@ -0,0 +1,145 @@
+/**
+ * @author Temdog007 / http://github.com/Temdog007
+ */
+
+Sidebar.Geometry.ExtrudeGeometry = function ( editor, object ) {
+
+	var strings = editor.strings;
+
+	var signals = editor.signals;
+
+	var container = new UI.Row();
+
+	var geometry = object.geometry;
+	var parameters = geometry.parameters;
+	var options = parameters.options;
+	options.curveSegments = options.curveSegments != undefined ? options.curveSegments : 12;
+	options.steps = options.steps != undefined ? options.steps : 1;
+	options.depth = options.depth != undefined ? options.depth : 100;
+	options.bevelThickness = options.bevelThickness !== undefined ? options.bevelThickness : 6;
+	options.bevelSize = options.bevelSize !== undefined ? options.bevelSize : 4;
+	options.bevelOffset = options.bevelOffset !== undefined ? options.bevelOffset : 0;
+	options.bevelSegments = options.bevelSegments !== undefined ? options.bevelSegments : 3;
+
+
+	// curveSegments
+
+	var curveSegmentsRow = new UI.Row();
+	var curveSegments = new UI.Integer( options.curveSegments ).onChange( update ).setRange( 1, Infinity );
+
+	curveSegmentsRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/extrude_geometry/curveSegments' ) ).setWidth( '90px' ) );
+	curveSegmentsRow.add( curveSegments );
+
+	container.add( curveSegmentsRow );
+
+	// steps
+
+	var stepsRow = new UI.Row();
+	var steps = new UI.Integer( options.steps ).onChange( update ).setRange( 1, Infinity );
+
+	stepsRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/extrude_geometry/steps' ) ).setWidth( '90px' ) );
+	stepsRow.add( steps );
+
+	container.add( stepsRow );
+
+	// depth
+
+	var depthRow = new UI.Row();
+	var depth = new UI.Number( options.depth ).onChange( update ).setRange( 1, Infinity );
+
+	depthRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/extrude_geometry/depth' ) ).setWidth( '90px' ) );
+	depthRow.add( depth );
+
+	container.add( depthRow );
+
+	// enabled
+
+	var enabledRow = new UI.Row();
+	var enabled = new UI.Checkbox( options.bevelEnabled ).onChange( update );
+
+	enabledRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/extrude_geometry/bevelEnabled' ) ).setWidth( '90px' ) );
+	enabledRow.add( enabled );
+
+	container.add( enabledRow );
+
+	if ( options.bevelEnabled === true ) {
+
+		// thickness
+
+		var thicknessRow = new UI.Row();
+		var thickness = new UI.Number( options.bevelThickness ).onChange( update );
+
+		thicknessRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/extrude_geometry/bevelThickness' ) ).setWidth( '90px' ) );
+		thicknessRow.add( thickness );
+
+		container.add( thicknessRow );
+
+		// size
+
+		var sizeRow = new UI.Row();
+		var size = new UI.Number( options.bevelSize ).onChange( update );
+
+		sizeRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/extrude_geometry/bevelSize' ) ).setWidth( '90px' ) );
+		sizeRow.add( size );
+
+		container.add( sizeRow );
+
+		// offset
+
+		var offsetRow = new UI.Row();
+		var offset = new UI.Number( options.bevelOffset ).onChange( update );
+
+		offsetRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/extrude_geometry/bevelOffset' ) ).setWidth( '90px' ) );
+		offsetRow.add( offset );
+
+		container.add( offsetRow );
+
+		// segments
+
+		var segmentsRow = new UI.Row();
+		var segments = new UI.Integer( options.bevelSegments ).onChange( update ).setRange( 0, Infinity );
+
+		segmentsRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/extrude_geometry/bevelSegments' ) ).setWidth( '90px' ) );
+		segmentsRow.add( segments );
+
+		container.add( segmentsRow );
+
+	}
+
+	var button = new UI.Button( strings.getKey( 'sidebar/geometry/extrude_geometry/shape' ) ).onClick( toShape ).setWidth( '90px' ).setMarginLeft( '90px' );
+	container.add( button );
+
+	//
+
+	function update() {
+
+		editor.execute( new SetGeometryCommand( object, new THREE[ geometry.type ](
+			parameters.shapes,
+			{
+				curveSegments: curveSegments.getValue(),
+				steps: steps.getValue(),
+				depth: depth.getValue(),
+				bevelEnabled: enabled.getValue(),
+				bevelThickness: thickness !== undefined ? thickness.getValue() : options.bevelThickness,
+				bevelSize: size !== undefined ? size.getValue() : options.bevelSize,
+				bevelOffset: offset !== undefined ? offset.getValue() : options.bevelOffset,
+				bevelSegments: segments !== undefined ? segments.getValue() : options.bevelSegments
+			}
+		) ) );
+
+	}
+
+	function toShape() {
+
+		editor.execute( new SetGeometryCommand( object, new THREE.ShapeBufferGeometry(
+			parameters.shapes,
+			options.curveSegments
+		) ) );
+
+	}
+
+	return container;
+
+};
+
+Sidebar.Geometry.ExtrudeBufferGeometry = Sidebar.Geometry.ExtrudeGeometry;

+ 55 - 0
editor/js/Sidebar.Geometry.ShapeGeometry.js

@@ -0,0 +1,55 @@
+/**
+ * @author Temdog007 / http://github.com/Temdog007
+ */
+
+Sidebar.Geometry.ShapeGeometry = function ( editor, object ) {
+
+	var strings = editor.strings;
+
+	var signals = editor.signals;
+
+	var container = new UI.Row();
+
+	var geometry = object.geometry;
+	var parameters = geometry.parameters;
+
+	// curveSegments
+
+	var curveSegmentsRow = new UI.Row();
+	var curveSegments = new UI.Integer( parameters.curveSegments || 12 ).onChange( changeShape ).setRange( 1, Infinity );
+
+	curveSegmentsRow.add( new UI.Text( strings.getKey( 'sidebar/geometry/shape_geometry/curveSegments' ) ).setWidth( '90px' ) );
+	curveSegmentsRow.add( curveSegments );
+
+	container.add( curveSegmentsRow );
+
+	// to extrude
+	var button = new UI.Button( strings.getKey( 'sidebar/geometry/shape_geometry/extrude' ) ).onClick( toExtrude ).setWidth( '90px' ).setMarginLeft( '90px' );
+	container.add( button );
+
+	//
+
+	function changeShape() {
+
+		editor.execute( new SetGeometryCommand( object, new THREE[ geometry.type ](
+			parameters.shapes,
+			curveSegments.getValue()
+		) ) );
+
+	}
+
+	function toExtrude() {
+
+		editor.execute( new SetGeometryCommand( object, new THREE.ExtrudeBufferGeometry(
+			parameters.shapes, {
+				curveSegments: curveSegments.getValue()
+			}
+		) ) );
+
+	}
+
+	return container;
+
+};
+
+Sidebar.Geometry.ShapeBufferGeometry = Sidebar.Geometry.ShapeGeometry;

+ 13 - 0
editor/js/Strings.js

@@ -135,6 +135,16 @@ var Strings = function ( config ) {
 			'sidebar/geometry/cylinder_geometry/heightsegments': 'Height segments',
 			'sidebar/geometry/cylinder_geometry/openended': 'Open ended',
 
+			'sidebar/geometry/extrude_geometry/curveSegments': 'Curve Segments',
+			'sidebar/geometry/extrude_geometry/steps': 'Steps',
+			'sidebar/geometry/extrude_geometry/depth': 'Depth',
+			'sidebar/geometry/extrude_geometry/bevelEnabled': 'Bevel?',
+			'sidebar/geometry/extrude_geometry/bevelThickness': 'Thickness',
+			'sidebar/geometry/extrude_geometry/bevelSize': 'Size',
+			'sidebar/geometry/extrude_geometry/bevelOffset': 'Offset',
+			'sidebar/geometry/extrude_geometry/bevelSegments': 'Segments',
+			'sidebar/geometry/extrude_geometry/shape': 'Convert to Shape',
+
 			'sidebar/geometry/geometry/vertices': 'Vertices',
 			'sidebar/geometry/geometry/faces': 'Faces',
 
@@ -164,6 +174,9 @@ var Strings = function ( config ) {
 			'sidebar/geometry/ring_geometry/thetastart': 'Theta start',
 			'sidebar/geometry/ring_geometry/thetalength': 'Theta length',
 
+			'sidebar/geometry/shape_geometry/curveSegments': 'Curve Segments',
+			'sidebar/geometry/shape_geometry/extrude': 'Extrude',
+
 			'sidebar/geometry/sphere_geometry/radius': 'Radius',
 			'sidebar/geometry/sphere_geometry/widthsegments': 'Width segments',
 			'sidebar/geometry/sphere_geometry/heightsegments': 'Height segments',

+ 1 - 0
examples/files.js

@@ -132,6 +132,7 @@ var files = {
 		"webgl_loader_texture_hdr",
 		"webgl_loader_texture_ktx",
 		"webgl_loader_texture_pvrtc",
+		"webgl_loader_texture_rgbm",
 		"webgl_loader_texture_tga",
 		"webgl_loader_ttf",
 		"webgl_loader_vrm",

+ 0 - 5
examples/index.html

@@ -55,11 +55,6 @@
 				<input placeholder="Search" type="text" id="filter" autocorrect="off" autocapitalize="off" spellcheck="false" />
 				<div id="exitSearchButton"></div>
 
-				<select id="language">
-					<option value="en">en</option>
-					<option value="zh">zh</option>
-				</select>
-
 				<div id="content"></div>
 			</div>
 

+ 252 - 150
examples/js/controls/TransformControls.js

@@ -53,8 +53,6 @@ THREE.TransformControls = function ( camera, domElement ) {
 		Y: new THREE.Vector3( 0, 1, 0 ),
 		Z: new THREE.Vector3( 0, 0, 1 )
 	};
-	var _identityQuaternion = new THREE.Quaternion();
-	var _alignVector = new THREE.Vector3();
 
 	var pointStart = new THREE.Vector3();
 	var pointEnd = new THREE.Vector3();
@@ -161,13 +159,13 @@ THREE.TransformControls = function ( camera, domElement ) {
 
 		Object.defineProperty( scope, propName, {
 
-			get: function() {
+			get: function () {
 
 				return propValue !== undefined ? propValue : defaultValue;
 
 			},
 
-			set: function( value ) {
+			set: function ( value ) {
 
 				if ( propValue !== value ) {
 
@@ -182,7 +180,7 @@ THREE.TransformControls = function ( camera, domElement ) {
 
 			}
 
-		});
+		} );
 
 		scope[ propName ] = defaultValue;
 		_plane[ propName ] = defaultValue;
@@ -221,7 +219,7 @@ THREE.TransformControls = function ( camera, domElement ) {
 
 	};
 
-	this.pointerHover = function( pointer ) {
+	this.pointerHover = function ( pointer ) {
 
 		if ( this.object === undefined || this.dragging === true || ( pointer.button !== undefined && pointer.button !== 0 ) ) return;
 
@@ -241,7 +239,7 @@ THREE.TransformControls = function ( camera, domElement ) {
 
 	};
 
-	this.pointerDown = function( pointer ) {
+	this.pointerDown = function ( pointer ) {
 
 		if ( this.object === undefined || this.dragging === true || ( pointer.button !== undefined && pointer.button !== 0 ) ) return;
 
@@ -255,11 +253,11 @@ THREE.TransformControls = function ( camera, domElement ) {
 
 				var space = this.space;
 
-				if ( this.mode === 'scale') {
+				if ( this.mode === 'scale' ) {
 
 					space = 'local';
 
-				} else if ( this.axis === 'E' ||  this.axis === 'XYZE' ||  this.axis === 'XYZ' ) {
+				} else if ( this.axis === 'E' || this.axis === 'XYZE' || this.axis === 'XYZ' ) {
 
 					space = 'world';
 
@@ -296,18 +294,18 @@ THREE.TransformControls = function ( camera, domElement ) {
 
 	};
 
-	this.pointerMove = function( pointer ) {
+	this.pointerMove = function ( pointer ) {
 
 		var axis = this.axis;
 		var mode = this.mode;
 		var object = this.object;
 		var space = this.space;
 
-		if ( mode === 'scale') {
+		if ( mode === 'scale' ) {
 
 			space = 'local';
 
-		} else if ( axis === 'E' ||  axis === 'XYZE' ||  axis === 'XYZ' ) {
+		} else if ( axis === 'E' || axis === 'XYZE' || axis === 'XYZ' ) {
 
 			space = 'world';
 
@@ -330,17 +328,23 @@ THREE.TransformControls = function ( camera, domElement ) {
 			offset.copy( pointEnd ).sub( pointStart );
 
 			if ( space === 'local' && axis !== 'XYZ' ) {
+
 				offset.applyQuaternion( worldQuaternionInv );
+
 			}
 
-			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 ( 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' ) {
 
-			if ( space === 'local' && axis !== 'XYZ') {
 				offset.applyQuaternion( quaternionStart ).divide( parentScale );
+
 			} else {
+
 				offset.applyQuaternion( parentQuaternionInv ).divide( parentScale );
+
 			}
 
 			object.position.copy( offset ).add( positionStart );
@@ -351,18 +355,24 @@ THREE.TransformControls = function ( camera, domElement ) {
 
 				if ( space === 'local' ) {
 
-					object.position.applyQuaternion(_tempQuaternion.copy( quaternionStart ).inverse() );
+					object.position.applyQuaternion( _tempQuaternion.copy( quaternionStart ).inverse() );
+
+					if ( axis.search( 'X' ) !== - 1 ) {
 
-					if ( axis.search( 'X' ) !== -1 ) {
 						object.position.x = Math.round( object.position.x / this.translationSnap ) * this.translationSnap;
+
 					}
 
-					if ( axis.search( 'Y' ) !== -1 ) {
+					if ( axis.search( 'Y' ) !== - 1 ) {
+
 						object.position.y = Math.round( object.position.y / this.translationSnap ) * this.translationSnap;
+
 					}
 
-					if ( axis.search( 'Z' ) !== -1 ) {
+					if ( axis.search( 'Z' ) !== - 1 ) {
+
 						object.position.z = Math.round( object.position.z / this.translationSnap ) * this.translationSnap;
+
 					}
 
 					object.position.applyQuaternion( quaternionStart );
@@ -372,23 +382,33 @@ THREE.TransformControls = function ( camera, domElement ) {
 				if ( space === 'world' ) {
 
 					if ( object.parent ) {
+
 						object.position.add( _tempVector.setFromMatrixPosition( object.parent.matrixWorld ) );
+
 					}
 
-					if ( axis.search( 'X' ) !== -1 ) {
+					if ( axis.search( 'X' ) !== - 1 ) {
+
 						object.position.x = Math.round( object.position.x / this.translationSnap ) * this.translationSnap;
+
 					}
 
-					if ( axis.search( 'Y' ) !== -1 ) {
+					if ( axis.search( 'Y' ) !== - 1 ) {
+
 						object.position.y = Math.round( object.position.y / this.translationSnap ) * this.translationSnap;
+
 					}
 
-					if ( axis.search( 'Z' ) !== -1 ) {
+					if ( axis.search( 'Z' ) !== - 1 ) {
+
 						object.position.z = Math.round( object.position.z / this.translationSnap ) * this.translationSnap;
+
 					}
 
 					if ( object.parent ) {
+
 						object.position.sub( _tempVector.setFromMatrixPosition( object.parent.matrixWorld ) );
+
 					}
 
 				}
@@ -397,32 +417,38 @@ THREE.TransformControls = function ( camera, domElement ) {
 
 		} else if ( mode === 'scale' ) {
 
-			if ( axis.search( 'XYZ' ) !== -1 ) {
+			if ( axis.search( 'XYZ' ) !== - 1 ) {
 
 				var d = pointEnd.length() / pointStart.length();
 
-				if ( pointEnd.dot( pointStart ) < 0 ) d *= -1;
+				if ( pointEnd.dot( pointStart ) < 0 ) d *= - 1;
 
 				_tempVector2.set( d, d, d );
 
 			} else {
 
-				_tempVector.copy(pointStart);
-				_tempVector2.copy(pointEnd);
+				_tempVector.copy( pointStart );
+				_tempVector2.copy( pointEnd );
 
 				_tempVector.applyQuaternion( worldQuaternionInv );
 				_tempVector2.applyQuaternion( worldQuaternionInv );
 
 				_tempVector2.divide( _tempVector );
 
-				if ( axis.search( 'X' ) === -1 ) {
+				if ( axis.search( 'X' ) === - 1 ) {
+
 					_tempVector2.x = 1;
+
 				}
-				if ( axis.search( 'Y' ) === -1 ) {
+				if ( axis.search( 'Y' ) === - 1 ) {
+
 					_tempVector2.y = 1;
+
 				}
-				if ( axis.search( 'Z' ) === -1 ) {
+				if ( axis.search( 'Z' ) === - 1 ) {
+
 					_tempVector2.z = 1;
+
 				}
 
 			}
@@ -445,11 +471,11 @@ THREE.TransformControls = function ( camera, domElement ) {
 				startNorm.copy( pointStart ).normalize();
 				endNorm.copy( pointEnd ).normalize();
 
-				rotationAngle *= ( endNorm.cross( startNorm ).dot( eye ) < 0 ? 1 : -1);
+				rotationAngle *= ( endNorm.cross( startNorm ).dot( eye ) < 0 ? 1 : - 1 );
 
 			} else if ( axis === 'XYZE' ) {
 
-				rotationAxis.copy( offset ).cross( eye ).normalize(  );
+				rotationAxis.copy( offset ).cross( eye ).normalize();
 				rotationAngle = offset.dot( _tempVector.copy( rotationAxis ).cross( this.eye ) ) * ROTATION_SPEED;
 
 			} else if ( axis === 'X' || axis === 'Y' || axis === 'Z' ) {
@@ -459,7 +485,9 @@ THREE.TransformControls = function ( camera, domElement ) {
 				_tempVector.copy( _unit[ axis ] );
 
 				if ( space === 'local' ) {
+
 					_tempVector.applyQuaternion( worldQuaternion );
+
 				}
 
 				rotationAngle = offset.dot( _tempVector.cross( eye ).normalize() ) * ROTATION_SPEED;
@@ -493,7 +521,7 @@ THREE.TransformControls = function ( camera, domElement ) {
 
 	};
 
-	this.pointerUp = function( pointer ) {
+	this.pointerUp = function ( pointer ) {
 
 		if ( pointer.button !== undefined && pointer.button !== 0 ) return;
 
@@ -530,7 +558,7 @@ THREE.TransformControls = function ( camera, domElement ) {
 
 	function onPointerHover( event ) {
 
-		if ( !scope.enabled ) return;
+		if ( ! scope.enabled ) return;
 
 		scope.pointerHover( getPointer( event ) );
 
@@ -538,7 +566,7 @@ THREE.TransformControls = function ( camera, domElement ) {
 
 	function onPointerDown( event ) {
 
-		if ( !scope.enabled ) return;
+		if ( ! scope.enabled ) return;
 
 		document.addEventListener( "mousemove", onPointerMove, false );
 
@@ -549,7 +577,7 @@ THREE.TransformControls = function ( camera, domElement ) {
 
 	function onPointerMove( event ) {
 
-		if ( !scope.enabled ) return;
+		if ( ! scope.enabled ) return;
 
 		scope.pointerMove( getPointer( event ) );
 
@@ -557,7 +585,7 @@ THREE.TransformControls = function ( camera, domElement ) {
 
 	function onPointerUp( event ) {
 
-		if ( !scope.enabled ) return;
+		if ( ! scope.enabled ) return;
 
 		document.removeEventListener( "mousemove", onPointerMove, false );
 
@@ -613,9 +641,9 @@ THREE.TransformControls = function ( camera, domElement ) {
 
 THREE.TransformControls.prototype = Object.assign( Object.create( THREE.Object3D.prototype ), {
 
-  constructor: THREE.TransformControls,
+	constructor: THREE.TransformControls,
 
-  isTransformControls: true
+	isTransformControls: true
 
 } );
 
@@ -630,21 +658,21 @@ THREE.TransformControlsGizmo = function () {
 
 	// shared materials
 
-	var gizmoMaterial = new THREE.MeshBasicMaterial({
+	var gizmoMaterial = new THREE.MeshBasicMaterial( {
 		depthTest: false,
 		depthWrite: false,
 		transparent: true,
 		side: THREE.DoubleSide,
 		fog: false
-	});
+	} );
 
-	var gizmoLineMaterial = new THREE.LineBasicMaterial({
+	var gizmoLineMaterial = new THREE.LineBasicMaterial( {
 		depthTest: false,
 		depthWrite: false,
 		transparent: true,
 		linewidth: 1,
 		fog: false
-	});
+	} );
 
 	// Make unique material for each axis/color
 
@@ -697,32 +725,32 @@ THREE.TransformControlsGizmo = function () {
 	matLineYellow.color.set( 0xffff00 );
 
 	var matLineGray = gizmoLineMaterial.clone();
-	matLineGray.color.set( 0x787878);
+	matLineGray.color.set( 0x787878 );
 
 	var matLineYellowTransparent = matLineYellow.clone();
 	matLineYellowTransparent.opacity = 0.25;
 
 	// reusable geometry
 
-	var arrowGeometry = new THREE.CylinderBufferGeometry( 0, 0.05, 0.2, 12, 1, false);
+	var arrowGeometry = new THREE.CylinderBufferGeometry( 0, 0.05, 0.2, 12, 1, false );
 
-	var scaleHandleGeometry = new THREE.BoxBufferGeometry( 0.125, 0.125, 0.125);
+	var scaleHandleGeometry = new THREE.BoxBufferGeometry( 0.125, 0.125, 0.125 );
 
 	var lineGeometry = new THREE.BufferGeometry( );
-	lineGeometry.addAttribute('position', new THREE.Float32BufferAttribute( [ 0, 0, 0,	1, 0, 0 ], 3 ) );
+	lineGeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( [ 0, 0, 0,	1, 0, 0 ], 3 ) );
 
-	var CircleGeometry = function( radius, arc ) {
+	var CircleGeometry = function ( radius, arc ) {
 
 		var geometry = new THREE.BufferGeometry( );
 		var vertices = [];
 
-		for ( var i = 0; i <= 64 * arc; ++i ) {
+		for ( var i = 0; i <= 64 * arc; ++ i ) {
 
 			vertices.push( 0, Math.cos( i / 32 * Math.PI ) * radius, Math.sin( i / 32 * Math.PI ) * radius );
 
 		}
 
-		geometry.addAttribute('position', new THREE.Float32BufferAttribute( vertices, 3 ) );
+		geometry.addAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
 
 		return geometry;
 
@@ -730,11 +758,11 @@ THREE.TransformControlsGizmo = function () {
 
 	// Special geometry for transform helper. If scaled with position vector it spans from [0,0,0] to position
 
-	var TranslateHelperGeometry = function( radius, arc ) {
+	var TranslateHelperGeometry = function () {
 
-		var geometry = new THREE.BufferGeometry()
+		var geometry = new THREE.BufferGeometry();
 
-		geometry.addAttribute('position', new THREE.Float32BufferAttribute( [ 0, 0, 0, 1, 1, 1 ], 3 ) );
+		geometry.addAttribute( 'position', new THREE.Float32BufferAttribute( [ 0, 0, 0, 1, 1, 1 ], 3 ) );
 
 		return geometry;
 
@@ -744,61 +772,61 @@ THREE.TransformControlsGizmo = 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, '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 ] ]
+			[ 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 ] ]
+			[ 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.OctahedronBufferGeometry( 0.1, 0 ), matWhiteTransperent ), [ 0, 0, 0 ], [ 0, 0, 0 ] ]
+			[ new THREE.Mesh( new THREE.OctahedronBufferGeometry( 0.1, 0 ), matWhiteTransperent ), [ 0, 0, 0 ], [ 0, 0, 0 ]]
 		],
 		XY: [
-			[ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.295, 0.295 ), matYellowTransparent ), [ 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 ] ]
+			[ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.295, 0.295 ), matYellowTransparent ), [ 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.PlaneBufferGeometry( 0.295, 0.295 ), matCyanTransparent ), [ 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 ] ]
+			[ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.295, 0.295 ), matCyanTransparent ), [ 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.PlaneBufferGeometry( 0.295, 0.295 ), matMagentaTransparent ), [ 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 ] ]
+			[ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.295, 0.295 ), matMagentaTransparent ), [ 0.15, 0, 0.15 ], [ - Math.PI / 2, 0, 0 ]],
+			[ new THREE.Line( lineGeometry, matLineMagenta ), [ 0.18, 0, 0.3 ], null, [ 0.125, 1, 1 ]],
+			[ new THREE.Line( lineGeometry, matLineMagenta ), [ 0.3, 0, 0.18 ], [ 0, - Math.PI / 2, 0 ], [ 0.125, 1, 1 ]]
 		]
 	};
 
 	var pickerTranslate = {
 		X: [
-			[ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.2, 0, 1, 4, 1, false ), matInvisible ), [ 0.6, 0, 0 ], [ 0, 0, -Math.PI / 2 ] ]
+			[ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.2, 0, 1, 4, 1, false ), matInvisible ), [ 0.6, 0, 0 ], [ 0, 0, - Math.PI / 2 ]]
 		],
 		Y: [
-			[ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.2, 0, 1, 4, 1, false ), matInvisible ), [ 0, 0.6, 0 ] ]
+			[ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.2, 0, 1, 4, 1, false ), matInvisible ), [ 0, 0.6, 0 ]]
 		],
 		Z: [
-			[ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.2, 0, 1, 4, 1, false ), matInvisible ), [ 0, 0, 0.6 ], [ Math.PI / 2, 0, 0 ] ]
+			[ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.2, 0, 1, 4, 1, false ), matInvisible ), [ 0, 0, 0.6 ], [ Math.PI / 2, 0, 0 ]]
 		],
 		XYZ: [
 			[ new THREE.Mesh( new THREE.OctahedronBufferGeometry( 0.2, 0 ), matInvisible ) ]
 		],
 		XY: [
-			[ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.4, 0.4 ), matInvisible ), [ 0.2, 0.2, 0 ] ]
+			[ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.4, 0.4 ), matInvisible ), [ 0.2, 0.2, 0 ]]
 		],
 		YZ: [
-			[ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.4, 0.4 ), matInvisible ), [ 0, 0.2, 0.2 ], [ 0, Math.PI / 2, 0 ] ]
+			[ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.4, 0.4 ), matInvisible ), [ 0, 0.2, 0.2 ], [ 0, Math.PI / 2, 0 ]]
 		],
 		XZ: [
-			[ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.4, 0.4 ), matInvisible ), [ 0.2, 0, 0.2 ], [ -Math.PI / 2, 0, 0 ] ]
+			[ new THREE.Mesh( new THREE.PlaneBufferGeometry( 0.4, 0.4 ), matInvisible ), [ 0.2, 0, 0.2 ], [ - Math.PI / 2, 0, 0 ]]
 		]
 	};
 
@@ -813,56 +841,56 @@ THREE.TransformControlsGizmo = function () {
 			[ new THREE.Line( TranslateHelperGeometry(), matHelper ), null, null, null, 'helper' ]
 		],
 		X: [
-			[ new THREE.Line( lineGeometry, matHelper.clone() ), [ -1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]
+			[ 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' ]
+			[ 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' ]
+			[ new THREE.Line( lineGeometry, matHelper.clone() ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ]
 		]
 	};
 
 	var gizmoRotate = {
 		X: [
 			[ new THREE.Line( CircleGeometry( 1, 0.5 ), matLineRed ) ],
-			[ new THREE.Mesh( new THREE.OctahedronBufferGeometry( 0.04, 0 ), matRed ), [ 0, 0, 0.99 ], null, [ 1, 3, 1 ] ],
+			[ new THREE.Mesh( new THREE.OctahedronBufferGeometry( 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.OctahedronBufferGeometry( 0.04, 0 ), matGreen ), [ 0, 0, 0.99 ], null, [ 3, 1, 1 ] ],
+			[ new THREE.Line( CircleGeometry( 1, 0.5 ), matLineGreen ), null, [ 0, 0, - Math.PI / 2 ]],
+			[ new THREE.Mesh( new THREE.OctahedronBufferGeometry( 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.OctahedronBufferGeometry( 0.04, 0 ), matBlue ), [ 0.99, 0, 0 ], null, [ 1, 3, 1 ] ],
+			[ new THREE.Line( CircleGeometry( 1, 0.5 ), matLineBlue ), null, [ 0, Math.PI / 2, 0 ]],
+			[ new THREE.Mesh( new THREE.OctahedronBufferGeometry( 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.CylinderBufferGeometry( 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.CylinderBufferGeometry( 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.CylinderBufferGeometry( 0.03, 0, 0.15, 4, 1, false ), matLineYellowTransparent ), [ 0, -1.17, 0 ], [ Math.PI, 0, 0 ], [ 1, 1, 0.001 ]],
+			[ new THREE.Line( CircleGeometry( 1.25, 1 ), matLineYellowTransparent ), null, [ 0, Math.PI / 2, 0 ]],
+			[ new THREE.Mesh( new THREE.CylinderBufferGeometry( 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.CylinderBufferGeometry( 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.CylinderBufferGeometry( 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.CylinderBufferGeometry( 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 ] ]
+			[ new THREE.Line( CircleGeometry( 1, 1 ), matLineGray ), null, [ 0, Math.PI / 2, 0 ]]
 		]
 	};
 
 	var helperRotate = {
 		AXIS: [
-			[ new THREE.Line( lineGeometry, matHelper.clone() ), [ -1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]
+			[ new THREE.Line( lineGeometry, matHelper.clone() ), [ - 1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]
 		]
 	};
 
 	var pickerRotate = {
 		X: [
-			[ new THREE.Mesh( new THREE.TorusBufferGeometry( 1, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, -Math.PI / 2, -Math.PI / 2 ] ],
+			[ new THREE.Mesh( new THREE.TorusBufferGeometry( 1, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, - Math.PI / 2, - Math.PI / 2 ]],
 		],
 		Y: [
-			[ new THREE.Mesh( new THREE.TorusBufferGeometry( 1, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ] ],
+			[ new THREE.Mesh( new THREE.TorusBufferGeometry( 1, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ Math.PI / 2, 0, 0 ]],
 		],
 		Z: [
-			[ new THREE.Mesh( new THREE.TorusBufferGeometry( 1, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, 0, -Math.PI / 2 ] ],
+			[ new THREE.Mesh( new THREE.TorusBufferGeometry( 1, 0.1, 4, 24 ), matInvisible ), [ 0, 0, 0 ], [ 0, 0, - Math.PI / 2 ]],
 		],
 		E: [
 			[ new THREE.Mesh( new THREE.TorusBufferGeometry( 1.25, 0.1, 2, 24 ), matInvisible ) ]
@@ -874,88 +902,88 @@ THREE.TransformControlsGizmo = function () {
 
 	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 ] ]
+			[ 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 ] ]
+			[ 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 ] ]
+			[ 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 ] ]
+			[ 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 ] ]
+			[ 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 ] ]
+			[ 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.BoxBufferGeometry( 0.125, 0.125, 0.125 ), matWhiteTransperent ), [ 1.1, 0, 0 ] ],
+			[ new THREE.Mesh( new THREE.BoxBufferGeometry( 0.125, 0.125, 0.125 ), matWhiteTransperent ), [ 1.1, 0, 0 ]],
 		],
 		XYZY: [
-			[ new THREE.Mesh( new THREE.BoxBufferGeometry( 0.125, 0.125, 0.125 ), matWhiteTransperent ), [ 0, 1.1, 0 ] ],
+			[ new THREE.Mesh( new THREE.BoxBufferGeometry( 0.125, 0.125, 0.125 ), matWhiteTransperent ), [ 0, 1.1, 0 ]],
 		],
 		XYZZ: [
-			[ new THREE.Mesh( new THREE.BoxBufferGeometry( 0.125, 0.125, 0.125 ), matWhiteTransperent ), [ 0, 0, 1.1 ] ],
+			[ new THREE.Mesh( new THREE.BoxBufferGeometry( 0.125, 0.125, 0.125 ), matWhiteTransperent ), [ 0, 0, 1.1 ]],
 		]
 	};
 
 	var pickerScale = {
 		X: [
-			[ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.2, 0, 0.8, 4, 1, false ), matInvisible ), [ 0.5, 0, 0 ], [ 0, 0, -Math.PI / 2 ] ]
+			[ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.2, 0, 0.8, 4, 1, false ), matInvisible ), [ 0.5, 0, 0 ], [ 0, 0, - Math.PI / 2 ]]
 		],
 		Y: [
-			[ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.2, 0, 0.8, 4, 1, false ), matInvisible ), [ 0, 0.5, 0 ] ]
+			[ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.2, 0, 0.8, 4, 1, false ), matInvisible ), [ 0, 0.5, 0 ]]
 		],
 		Z: [
-			[ new THREE.Mesh( new THREE.CylinderBufferGeometry( 0.2, 0, 0.8, 4, 1, false ), matInvisible ), [ 0, 0, 0.5 ], [ Math.PI / 2, 0, 0 ] ]
+			[ new THREE.Mesh( new THREE.CylinderBufferGeometry( 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 ] ],
+			[ 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 ] ],
+			[ 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 ] ],
+			[ new THREE.Mesh( scaleHandleGeometry, matInvisible ), [ 0.85, 0, 0.85 ], null, [ 3, 0.2, 3 ]],
 		],
 		XYZX: [
-			[ new THREE.Mesh( new THREE.BoxBufferGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 1.1, 0, 0 ] ],
+			[ new THREE.Mesh( new THREE.BoxBufferGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 1.1, 0, 0 ]],
 		],
 		XYZY: [
-			[ new THREE.Mesh( new THREE.BoxBufferGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 0, 1.1, 0 ] ],
+			[ new THREE.Mesh( new THREE.BoxBufferGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 0, 1.1, 0 ]],
 		],
 		XYZZ: [
-			[ new THREE.Mesh( new THREE.BoxBufferGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 0, 0, 1.1 ] ],
+			[ new THREE.Mesh( new THREE.BoxBufferGeometry( 0.2, 0.2, 0.2 ), matInvisible ), [ 0, 0, 1.1 ]],
 		]
 	};
 
 	var helperScale = {
 		X: [
-			[ new THREE.Line( lineGeometry, matHelper.clone() ), [ -1e3, 0, 0 ], null, [ 1e6, 1, 1 ], 'helper' ]
+			[ 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' ]
+			[ 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' ]
+			[ new THREE.Line( lineGeometry, matHelper.clone() ), [ 0, 0, - 1e3 ], [ 0, - Math.PI / 2, 0 ], [ 1e6, 1, 1 ], 'helper' ]
 		]
 	};
 
 	// Creates an Object3D with gizmos described in custom hierarchy definition.
 
-	var setupGizmo = function( gizmoMap ) {
+	var setupGizmo = function ( gizmoMap ) {
 
 		var gizmo = new THREE.Object3D();
 
@@ -973,28 +1001,34 @@ THREE.TransformControlsGizmo = function () {
 				object.name = name;
 				object.tag = tag;
 
-				if (position) {
-					object.position.set(position[ 0 ], position[ 1 ], position[ 2 ]);
+				if ( position ) {
+
+					object.position.set( position[ 0 ], position[ 1 ], position[ 2 ] );
+
 				}
-				if (rotation) {
-					object.rotation.set(rotation[ 0 ], rotation[ 1 ], rotation[ 2 ]);
+				if ( rotation ) {
+
+					object.rotation.set( rotation[ 0 ], rotation[ 1 ], rotation[ 2 ] );
+
 				}
-				if (scale) {
-					object.scale.set(scale[ 0 ], scale[ 1 ], scale[ 2 ]);
+				if ( scale ) {
+
+					object.scale.set( scale[ 0 ], scale[ 1 ], scale[ 2 ] );
+
 				}
 
 				object.updateMatrix();
 
 				var tempGeometry = object.geometry.clone();
-				tempGeometry.applyMatrix(object.matrix);
+				tempGeometry.applyMatrix( 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);
+				object.scale.set( 1, 1, 1 );
 
-				gizmo.add(object);
+				gizmo.add( object );
 
 			}
 
@@ -1067,9 +1101,9 @@ THREE.TransformControlsGizmo = function () {
 		handles = handles.concat( this.gizmo[ this.mode ].children );
 		handles = handles.concat( this.helper[ this.mode ].children );
 
-		for ( var i = 0; i < handles.length; i++ ) {
+		for ( var i = 0; i < handles.length; i ++ ) {
 
-			var handle = handles[i];
+			var handle = handles[ i ];
 
 			// hide aligned to camera
 
@@ -1077,7 +1111,7 @@ THREE.TransformControlsGizmo = function () {
 			handle.rotation.set( 0, 0, 0 );
 			handle.position.copy( this.worldPosition );
 
-			var eyeDistance = this.worldPosition.distanceTo( this.cameraPosition);
+			var eyeDistance = this.worldPosition.distanceTo( this.cameraPosition );
 			handle.scale.set( 1, 1, 1 ).multiplyScalar( eyeDistance * this.size / 7 );
 
 			// TODO: simplify helpers and consider decoupling from gizmo
@@ -1089,7 +1123,7 @@ THREE.TransformControlsGizmo = function () {
 				if ( handle.name === 'AXIS' ) {
 
 					handle.position.copy( this.worldPositionStart );
-					handle.visible = !!this.axis;
+					handle.visible = !! this.axis;
 
 					if ( this.axis === 'X' ) {
 
@@ -1097,7 +1131,9 @@ THREE.TransformControlsGizmo = function () {
 						handle.quaternion.copy( quaternion ).multiply( tempQuaternion );
 
 						if ( Math.abs( alignVector.copy( unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) {
+
 							handle.visible = false;
+
 						}
 
 					}
@@ -1108,7 +1144,9 @@ THREE.TransformControlsGizmo = function () {
 						handle.quaternion.copy( quaternion ).multiply( tempQuaternion );
 
 						if ( Math.abs( alignVector.copy( unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) {
+
 							handle.visible = false;
+
 						}
 
 					}
@@ -1119,7 +1157,9 @@ THREE.TransformControlsGizmo = function () {
 						handle.quaternion.copy( quaternion ).multiply( tempQuaternion );
 
 						if ( Math.abs( alignVector.copy( unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) > 0.9 ) {
+
 							handle.visible = false;
+
 						}
 
 					}
@@ -1155,7 +1195,7 @@ THREE.TransformControlsGizmo = function () {
 
 					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.set( 1e-10, 1e-10, 1e-10 ).add( this.worldPositionStart ).sub( this.worldPosition ).multiplyScalar( - 1 );
 					tempVector.applyQuaternion( this.worldQuaternionStart.clone().inverse() );
 					handle.scale.copy( tempVector );
 					handle.visible = this.dragging;
@@ -1176,7 +1216,7 @@ THREE.TransformControlsGizmo = function () {
 
 					if ( this.axis ) {
 
-						handle.visible = this.axis.search( handle.name ) !== -1;
+						handle.visible = this.axis.search( handle.name ) !== - 1;
 
 					}
 
@@ -1201,78 +1241,132 @@ THREE.TransformControlsGizmo = function () {
 
 
 				if ( handle.name === 'X' || handle.name === 'XYZX' ) {
+
 					if ( Math.abs( alignVector.copy( unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) {
+
 						handle.scale.set( 1e-10, 1e-10, 1e-10 );
 						handle.visible = false;
+
 					}
+
 				}
 				if ( handle.name === 'Y' || handle.name === 'XYZY' ) {
+
 					if ( Math.abs( alignVector.copy( unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) {
+
 						handle.scale.set( 1e-10, 1e-10, 1e-10 );
 						handle.visible = false;
+
 					}
+
 				}
 				if ( handle.name === 'Z' || handle.name === 'XYZZ' ) {
+
 					if ( Math.abs( alignVector.copy( unitZ ).applyQuaternion( quaternion ).dot( this.eye ) ) > AXIS_HIDE_TRESHOLD ) {
+
 						handle.scale.set( 1e-10, 1e-10, 1e-10 );
 						handle.visible = false;
+
 					}
+
 				}
 				if ( handle.name === 'XY' ) {
+
 					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;
+
 					}
+
 				}
 				if ( handle.name === 'YZ' ) {
+
 					if ( Math.abs( alignVector.copy( unitX ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_TRESHOLD ) {
+
 						handle.scale.set( 1e-10, 1e-10, 1e-10 );
 						handle.visible = false;
+
 					}
+
 				}
 				if ( handle.name === 'XZ' ) {
+
 					if ( Math.abs( alignVector.copy( unitY ).applyQuaternion( quaternion ).dot( this.eye ) ) < PLANE_HIDE_TRESHOLD ) {
+
 						handle.scale.set( 1e-10, 1e-10, 1e-10 );
 						handle.visible = false;
+
 					}
+
 				}
 
 				// Flip translate and scale axis ocluded behind another axis
 
-				if ( handle.name.search( 'X' ) !== -1 ) {
+				if ( handle.name.search( 'X' ) !== - 1 ) {
+
 					if ( alignVector.copy( unitX ).applyQuaternion( quaternion ).dot( this.eye ) < AXIS_FLIP_TRESHOLD ) {
+
 						if ( handle.tag === 'fwd' ) {
+
 							handle.visible = false;
+
 						} else {
-							handle.scale.x *= -1;
+
+							handle.scale.x *= - 1;
+
 						}
+
 					} else if ( handle.tag === 'bwd' ) {
+
 						handle.visible = false;
+
 					}
+
 				}
 
-				if ( handle.name.search( 'Y' ) !== -1 ) {
+				if ( handle.name.search( 'Y' ) !== - 1 ) {
+
 					if ( alignVector.copy( unitY ).applyQuaternion( quaternion ).dot( this.eye ) < AXIS_FLIP_TRESHOLD ) {
+
 						if ( handle.tag === 'fwd' ) {
+
 							handle.visible = false;
+
 						} else {
-							handle.scale.y *= -1;
+
+							handle.scale.y *= - 1;
+
 						}
+
 					} else if ( handle.tag === 'bwd' ) {
+
 						handle.visible = false;
+
 					}
+
 				}
 
-				if ( handle.name.search( 'Z' ) !== -1 ) {
+				if ( handle.name.search( 'Z' ) !== - 1 ) {
+
 					if ( alignVector.copy( unitZ ).applyQuaternion( quaternion ).dot( this.eye ) < AXIS_FLIP_TRESHOLD ) {
+
 						if ( handle.tag === 'fwd' ) {
+
 							handle.visible = false;
+
 						} else {
-							handle.scale.z *= -1;
+
+							handle.scale.z *= - 1;
+
 						}
+
 					} else if ( handle.tag === 'bwd' ) {
+
 						handle.visible = false;
+
 					}
+
 				}
 
 			} else if ( this.mode === 'rotate' ) {
@@ -1290,7 +1384,7 @@ THREE.TransformControlsGizmo = function () {
 
 				if ( handle.name === 'X' ) {
 
-					tempQuaternion.setFromAxisAngle( unitX, Math.atan2( -alignVector.y, alignVector.z ) );
+					tempQuaternion.setFromAxisAngle( unitX, Math.atan2( - alignVector.y, alignVector.z ) );
 					tempQuaternion.multiplyQuaternions( tempQuaternion2, tempQuaternion );
 					handle.quaternion.copy( tempQuaternion );
 
@@ -1315,10 +1409,10 @@ THREE.TransformControlsGizmo = function () {
 			}
 
 			// 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 ) );
+			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
 
@@ -1328,7 +1422,7 @@ THREE.TransformControlsGizmo = function () {
 			handle.material.color.copy( handle.material._color );
 			handle.material.opacity = handle.material._opacity;
 
-			if ( !this.enabled ) {
+			if ( ! this.enabled ) {
 
 				handle.material.opacity *= 0.5;
 				handle.material.color.lerp( new THREE.Color( 1, 1, 1 ), 0.5 );
@@ -1340,7 +1434,11 @@ THREE.TransformControlsGizmo = function () {
 					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 ) { return handle.name === a; } ) ) {
+				} else if ( this.axis.split( '' ).some( function ( a ) {
+
+					return handle.name === a;
+
+				} ) ) {
 
 					handle.material.opacity = 1.0;
 					handle.material.color.lerp( new THREE.Color( 1, 1, 1 ), 0.5 );
@@ -1392,7 +1490,7 @@ THREE.TransformControlsPlane = function () {
 	var tempMatrix = new THREE.Matrix4();
 	var identityQuaternion = new THREE.Quaternion();
 
-	this.updateMatrixWorld = function() {
+	this.updateMatrixWorld = function () {
 
 		var space = this.space;
 
@@ -1409,9 +1507,11 @@ THREE.TransformControlsPlane = function () {
 		alignVector.copy( unitY );
 
 		switch ( this.mode ) {
+
 			case 'translate':
 			case 'scale':
 				switch ( this.axis ) {
+
 					case 'X':
 						alignVector.copy( this.eye ).cross( unitX );
 						dirVector.copy( unitX ).cross( alignVector );
@@ -1438,12 +1538,14 @@ THREE.TransformControlsPlane = function () {
 					case 'E':
 						dirVector.set( 0, 0, 0 );
 						break;
+
 				}
 				break;
 			case 'rotate':
 			default:
 				// special case for rotate
 				dirVector.set( 0, 0, 0 );
+
 		}
 
 		if ( dirVector.length() === 0 ) {

+ 24 - 8
examples/js/loaders/3MFLoader.js

@@ -1,5 +1,17 @@
 /**
  * @author technohippy / https://github.com/technohippy
+ * @author Mugen87 / https://github.com/Mugen87
+ *
+ * 3D Manufacturing Format (3MF) specification: https://3mf.io/specification/
+ *
+ * The following features from the core specification are supported:
+ *
+ * - 3D Models
+ * - Object Resources (Meshes and Components)
+ * - Material Resources (Base Materials)
+ *
+ * 3MF Materials and Properties Extension (e.g. textures) are not yet supported.
+ *
  */
 
 THREE.ThreeMFLoader = function ( manager ) {
@@ -63,7 +75,7 @@ THREE.ThreeMFLoader.prototype = {
 
 				if ( e instanceof ReferenceError ) {
 
-					console.error( 'THREE.ThreeMFLoader: jszip missing and file is compressed.' );
+					console.error( 'THREE.3MFLoader: jszip missing and file is compressed.' );
 					return null;
 
 				}
@@ -110,7 +122,7 @@ THREE.ThreeMFLoader.prototype = {
 
 				if ( xmlData.documentElement.nodeName.toLowerCase() !== 'model' ) {
 
-					console.error( 'THREE.ThreeMFLoader: Error loading 3MF - no 3MF document found: ', modelPart );
+					console.error( 'THREE.3MFLoader: Error loading 3MF - no 3MF document found: ', modelPart );
 
 				}
 
@@ -596,6 +608,8 @@ THREE.ThreeMFLoader.prototype = {
 
 			var material;
 
+			// add material if an object-level definition is present
+
 			if ( basematerialsData && ( basematerialsData.id === objectData.pid ) ) {
 
 				var materialIndex = objectData.pindex;
@@ -603,7 +617,11 @@ THREE.ThreeMFLoader.prototype = {
 
 				material = getBuild( basematerialData, objects, modelData, objectData, buildBasematerial );
 
-			} else if ( geometry.groups.length > 0 ) {
+			}
+
+			// add/overwrite material if definitions on triangles are present
+
+			if ( geometry.groups.length > 0 ) {
 
 				var groups = geometry.groups;
 				material = [];
@@ -617,13 +635,11 @@ THREE.ThreeMFLoader.prototype = {
 
 				}
 
-			} else {
-
-				// default material
+			}
 
-				material = new THREE.MeshPhongMaterial( { color: 0xaaaaff, flatShading: true } );
+			// default material
 
-			}
+			if ( material === undefined ) material = new THREE.MeshPhongMaterial( { color: 0xaaaaff, flatShading: true } );
 
 			return new THREE.Mesh( geometry, material );
 

+ 53 - 0
examples/js/loaders/ColladaLoader.js

@@ -3814,6 +3814,35 @@ THREE.ColladaLoader.prototype = {
 
 		}
 
+		// convert the parser error element into text with each child elements text
+		// separated by new lines.
+
+		function parserErrorToText( parserError ) {
+
+			var result = '';
+			var stack = [ parserError ];
+
+			while ( stack.length ) {
+
+				var node = stack.shift();
+
+				if ( node.nodeType === Node.TEXT_NODE ) {
+
+					result += node.textContent;
+
+				} else {
+
+					result += '\n';
+					stack.push.apply( stack, node.childNodes );
+
+				}
+
+			}
+
+			return result.trim();
+
+		}
+
 		if ( text.length === 0 ) {
 
 			return { scene: new THREE.Scene() };
@@ -3824,6 +3853,30 @@ THREE.ColladaLoader.prototype = {
 
 		var collada = getElementsByTagName( xml, 'COLLADA' )[ 0 ];
 
+		var parserError = xml.getElementsByTagName( 'parsererror' )[ 0 ];
+		if ( parserError !== undefined ) {
+
+			// Chrome will return parser error with a div in it
+
+			var errorElement = getElementsByTagName( parserError, 'div' )[ 0 ];
+			var errorText;
+
+			if ( errorElement ) {
+
+				errorText = errorElement.textContent;
+
+			} else {
+
+				errorText = parserErrorToText( parserError );
+
+			}
+
+			console.error( 'THREE.ColladaLoader: Failed to parse collada file.\n', errorText );
+
+			return null;
+
+		}
+
 		// metadata
 
 		var version = collada.getAttribute( 'version' );

+ 433 - 157
examples/js/loaders/LDrawLoader.js

@@ -7,6 +7,201 @@
 
 THREE.LDrawLoader = ( function () {
 
+	var tempVec0 = new THREE.Vector3();
+	var tempVec1 = new THREE.Vector3();
+	function smoothNormals( triangles, lineSegments ) {
+
+		function hashVertex( v ) {
+
+			// NOTE: 1e2 is pretty coarse but was chosen because it allows edges
+			// to be smoothed as expected (see minifig arms). The errors between edges
+			// could be due to matrix multiplication.
+			var x = ~ ~ ( v.x * 1e2 );
+			var y = ~ ~ ( v.y * 1e2 );
+			var z = ~ ~ ( v.z * 1e2 );
+			return `${ x },${ y },${ z }`;
+
+		}
+
+		function hashEdge( v0, v1 ) {
+
+			return `${ hashVertex( v0 ) }_${ hashVertex( v1 ) }`;
+
+		}
+
+		var hardEdges = new Set();
+		var halfEdgeList = {};
+		var fullHalfEdgeList = {};
+		var normals = [];
+
+		// Save the list of hard edges by hash
+		for ( var i = 0, l = lineSegments.length; i < l; i ++ ) {
+
+			var ls = lineSegments[ i ];
+			var v0 = ls.v0;
+			var v1 = ls.v1;
+			hardEdges.add( hashEdge( v0, v1 ) );
+			hardEdges.add( hashEdge( v1, v0 ) );
+
+		}
+
+		// track the half edges associated with each triangle
+		for ( var i = 0, l = triangles.length; i < l; i ++ ) {
+
+			var tri = triangles[ i ];
+			for ( var i2 = 0, l2 = 3; i2 < l2; i2 ++ ) {
+
+				var index = i2;
+				var next = ( i2 + 1 ) % 3;
+				var v0 = tri[ `v${ index }` ];
+				var v1 = tri[ `v${ next }` ];
+				var hash = hashEdge( v0, v1 );
+
+				// don't add the triangle if the edge is supposed to be hard
+				if ( hardEdges.has( hash ) ) continue;
+				halfEdgeList[ hash ] = tri;
+				fullHalfEdgeList[ hash ] = tri;
+
+			}
+
+		}
+
+		// NOTE: Some of the normals wind up being skewed in an unexpected way because
+		// quads provide more "influence" to some vertex normals than a triangle due to
+		// the fact that a quad is made up of two triangles and all triangles are weighted
+		// equally. To fix this quads could be tracked separately so their vertex normals
+		// are weighted appropriately or we could try only adding a normal direction
+		// once per normal.
+
+		// Iterate until we've tried to connect all triangles to share normals
+		while ( true ) {
+
+			// Stop if there are no more triangles left
+			var halfEdges = Object.keys( halfEdgeList );
+			if ( halfEdges.length === 0 ) break;
+
+			// Exhaustively find all connected triangles
+			var i = 0;
+			var queue = [ fullHalfEdgeList[ halfEdges[ 0 ] ] ];
+			while ( i < queue.length ) {
+
+				// initialize all vertex normals in this triangle
+				var tri = queue[ i ];
+				i ++;
+
+				var faceNormal = tri.faceNormal;
+				if ( tri.n0 === null ) {
+
+					tri.n0 = faceNormal.clone();
+					normals.push( tri.n0 );
+
+				}
+
+				if ( tri.n1 === null ) {
+
+					tri.n1 = faceNormal.clone();
+					normals.push( tri.n1 );
+
+				}
+
+				if ( tri.n2 === null ) {
+
+					tri.n2 = faceNormal.clone();
+					normals.push( tri.n2 );
+
+				}
+
+				// Check if any edge is connected to another triangle edge
+				for ( var i2 = 0, l2 = 3; i2 < l2; i2 ++ ) {
+
+					var index = i2;
+					var next = ( i2 + 1 ) % 3;
+					var v0 = tri[ `v${ index }` ];
+					var v1 = tri[ `v${ next }` ];
+
+					// delete this triangle from the list so it won't be found again
+					var hash = hashEdge( v0, v1 );
+					delete halfEdgeList[ hash ];
+
+					var reverseHash = hashEdge( v1, v0 );
+					var otherTri = fullHalfEdgeList[ reverseHash ];
+					if ( otherTri ) {
+
+						// NOTE: If the angle between triangles is > 67.5 degrees then assume it's
+						// hard edge. There are some cases where the line segments do not line up exactly
+						// with or span multiple triangle edges (see Lunar Vehicle wheels).
+						if ( Math.abs( otherTri.faceNormal.dot( tri.faceNormal ) ) < 0.25 ) {
+
+							continue;
+
+						}
+
+						// if this triangle has already been traversed then it won't be in
+						// the halfEdgeList. If it has not then add it to the queue and delete
+						// it so it won't be found again.
+						if ( reverseHash in halfEdgeList ) {
+
+							queue.push( otherTri );
+							delete halfEdgeList[ reverseHash ];
+
+						}
+
+						// Find the matching edge in this triangle and copy the normal vector over
+						for ( var i3 = 0, l3 = 3; i3 < l3; i3 ++ ) {
+
+							var otherIndex = i3;
+							var otherNext = ( i3 + 1 ) % 3;
+							var otherV0 = otherTri[ `v${ otherIndex }` ];
+							var otherV1 = otherTri[ `v${ otherNext }` ];
+
+							var otherHash = hashEdge( otherV0, otherV1 );
+							if ( otherHash === reverseHash ) {
+
+								if ( otherTri[ `n${ otherIndex }` ] === null ) {
+
+									var norm = tri[ `n${ next }` ];
+									otherTri[ `n${ otherIndex }` ] = norm;
+									norm.add( otherTri.faceNormal );
+
+								}
+
+								if ( otherTri[ `n${ otherNext }` ] === null ) {
+
+									var norm = tri[ `n${ index }` ];
+									otherTri[ `n${ otherNext }` ] = norm;
+									norm.add( otherTri.faceNormal );
+
+								}
+
+								break;
+
+							}
+
+						}
+
+					}
+
+				}
+
+			}
+
+		}
+
+		// The normals of each face have been added up so now we average them by normalizing the vector.
+		for ( var i = 0, l = normals.length; i < l; i ++ ) {
+
+			normals[ i ].normalize();
+
+		}
+
+	}
+
+	function isPrimitiveType( type ) {
+
+		return /primitive/i.test( type ) || type === 'Subpart';
+
+	}
+
 	function LineParser( line, lineNumber ) {
 
 		this.line = line;
@@ -119,11 +314,11 @@ THREE.LDrawLoader = ( function () {
 		// Sort the triangles or line segments by colour code to make later the mesh groups
 		elements.sort( sortByMaterial );
 
-		var vertices = [];
+		var positions = [];
+		var normals = [];
 		var materials = [];
 
 		var bufferGeometry = new THREE.BufferGeometry();
-		bufferGeometry.clearGroups();
 		var prevMaterial = null;
 		var index0 = 0;
 		var numGroupVerts = 0;
@@ -134,10 +329,17 @@ THREE.LDrawLoader = ( function () {
 			var v0 = elem.v0;
 			var v1 = elem.v1;
 			// Note that LDraw coordinate system is rotated 180 deg. in the X axis w.r.t. Three.js's one
-			vertices.push( v0.x, v0.y, v0.z, v1.x, v1.y, v1.z );
+			positions.push( v0.x, v0.y, v0.z, v1.x, v1.y, v1.z );
 			if ( elementSize === 3 ) {
 
-				vertices.push( elem.v2.x, elem.v2.y, elem.v2.z );
+				positions.push( elem.v2.x, elem.v2.y, elem.v2.z );
+
+				var n0 = elem.n0 || elem.faceNormal;
+				var n1 = elem.n1 || elem.faceNormal;
+				var n2 = elem.n2 || elem.faceNormal;
+				normals.push( n0.x, n0.y, n0.z );
+				normals.push( n1.x, n1.y, n1.z );
+				normals.push( n2.x, n2.y, n2.z );
 
 			}
 
@@ -169,7 +371,13 @@ THREE.LDrawLoader = ( function () {
 
 		}
 
-		bufferGeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( vertices, 3 ) );
+		bufferGeometry.addAttribute( 'position', new THREE.Float32BufferAttribute( positions, 3 ) );
+
+		if ( elementSize === 3 ) {
+
+			bufferGeometry.addAttribute( 'normal', new THREE.Float32BufferAttribute( normals, 3 ) );
+
+		}
 
 		var object3d = null;
 
@@ -179,8 +387,6 @@ THREE.LDrawLoader = ( function () {
 
 		} else if ( elementSize === 3 ) {
 
-			bufferGeometry.computeVertexNormals();
-
 			object3d = new THREE.Mesh( bufferGeometry, materials );
 
 		}
@@ -223,10 +429,8 @@ THREE.LDrawLoader = ( function () {
 		// If not (the default), only one object which contains all the merged primitives will be created.
 		this.separateObjects = false;
 
-		// Current merged object and primitives
-		this.currentGroupObject = null;
-		this.currentTriangles = null;
-		this.currentLineSegments = null;
+		// If this flag is set to true the vertex normals will be smoothed.
+		this.smoothNormals = true;
 
 	}
 
@@ -278,6 +482,15 @@ THREE.LDrawLoader = ( function () {
 
 				var parentParseScope = scope.getParentParseScope();
 
+				// Set current matrix
+				if ( subobject ) {
+
+					parseScope.currentMatrix.multiplyMatrices( parentParseScope.currentMatrix, subobject.matrix );
+					parseScope.matrix.copy( subobject.matrix );
+					parseScope.inverted = subobject.inverted;
+
+				}
+
 				// Add to cache
 				var currentFileName = parentParseScope.currentFileName;
 				if ( currentFileName !== null ) {
@@ -292,21 +505,12 @@ THREE.LDrawLoader = ( function () {
 
 				}
 
-				parseScope.inverted = subobject !== undefined ? subobject.inverted : false;
 
 				// Parse the object (returns a THREE.Group)
-				var objGroup = scope.parse( text );
-
-				// Load subobjects
-				parseScope.subobjects = objGroup.userData.subobjects;
-				parseScope.numSubobjects = parseScope.subobjects.length;
-				parseScope.subobjectIndex = 0;
-
+				scope.parse( text );
 				var finishedCount = 0;
 				onSubobjectFinish();
 
-				return objGroup;
-
 				function onSubobjectFinish() {
 
 					finishedCount ++;
@@ -337,18 +541,101 @@ THREE.LDrawLoader = ( function () {
 
 				function finalizeObject() {
 
-					if ( ! scope.separateObjects && ! parentParseScope.isFromParse ) {
+					if ( scope.smoothNormals && parseScope.type === 'Part' ) {
+
+						smoothNormals( parseScope.triangles, parseScope.lineSegments );
+
+					}
 
-						// We are finalizing the root object and merging primitives is activated, so create the entire Mesh and LineSegments objects now
-						if ( scope.currentLineSegments.length > 0 ) {
+					var isRoot = ! parentParseScope.isFromParse;
+					if ( scope.separateObjects && ! isPrimitiveType( parseScope.type ) || isRoot ) {
 
-							objGroup.add( createObject( scope.currentLineSegments, 2 ) );
+
+						const objGroup = parseScope.groupObject;
+						if ( parseScope.triangles.length > 0 ) {
+
+							objGroup.add( createObject( parseScope.triangles, 3 ) );
 
 						}
 
-						if ( scope.currentTriangles.length > 0 ) {
+						if ( parseScope.lineSegments.length > 0 ) {
 
-							objGroup.add( createObject( scope.currentTriangles, 3 ) );
+							objGroup.add( createObject( parseScope.lineSegments, 2 ) );
+
+						}
+
+						if ( parseScope.conditionalSegments.length > 0 ) {
+
+							var lines = createObject( parseScope.conditionalSegments, 2 );
+							lines.isConditionalLine = true;
+							lines.visible = false;
+							objGroup.add( lines );
+
+						}
+
+						if ( parentParseScope.groupObject ) {
+
+							objGroup.name = parseScope.fileName;
+							objGroup.userData.category = parseScope.category;
+							objGroup.userData.keywords = parseScope.keywords;
+							parseScope.matrix.decompose( objGroup.position, objGroup.quaternion, objGroup.scale );
+
+							parentParseScope.groupObject.add( objGroup );
+
+						}
+
+					} else {
+
+						var separateObjects = scope.separateObjects;
+						var parentLineSegments = parentParseScope.lineSegments;
+						var parentConditionalSegments = parentParseScope.conditionalSegments;
+						var parentTriangles = parentParseScope.triangles;
+
+						var lineSegments = parseScope.lineSegments;
+						var conditionalSegments = parseScope.conditionalSegments;
+						var triangles = parseScope.triangles;
+
+						for ( var i = 0, l = lineSegments.length; i < l; i ++ ) {
+
+							var ls = lineSegments[ i ];
+							if ( separateObjects ) {
+
+								ls.v0.applyMatrix4( parseScope.matrix );
+								ls.v1.applyMatrix4( parseScope.matrix );
+
+							}
+							parentLineSegments.push( ls );
+
+						}
+
+						for ( var i = 0, l = conditionalSegments.length; i < l; i ++ ) {
+
+							var os = conditionalSegments[ i ];
+							if ( separateObjects ) {
+
+								os.v0.applyMatrix4( parseScope.matrix );
+								os.v1.applyMatrix4( parseScope.matrix );
+
+							}
+							parentConditionalSegments.push( os );
+
+						}
+
+						for ( var i = 0, l = triangles.length; i < l; i ++ ) {
+
+							var tri = triangles[ i ];
+							if ( separateObjects ) {
+
+								tri.v0 = tri.v0.clone().applyMatrix4( parseScope.matrix );
+								tri.v1 = tri.v1.clone().applyMatrix4( parseScope.matrix );
+								tri.v2 = tri.v2.clone().applyMatrix4( parseScope.matrix );
+
+								tempVec0.subVectors( tri.v1, tri.v0 );
+								tempVec1.subVectors( tri.v2, tri.v1 );
+								tri.faceNormal.crossVectors( tempVec0, tempVec1 ).normalize();
+
+							}
+							parentTriangles.push( tri );
 
 						}
 
@@ -358,7 +645,7 @@ THREE.LDrawLoader = ( function () {
 
 					if ( onProcessed ) {
 
-						onProcessed( objGroup );
+						onProcessed( parseScope.groupObject );
 
 					}
 
@@ -370,12 +657,6 @@ THREE.LDrawLoader = ( function () {
 					parseScope.mainEdgeColourCode = subobject.material.userData.edgeMaterial.userData.code;
 					parseScope.currentFileName = subobject.originalFileName;
 
-					if ( ! scope.separateObjects ) {
-
-						// Set current matrix
-						parseScope.currentMatrix.multiplyMatrices( parentParseScope.currentMatrix, subobject.matrix );
-
-					}
 
 					// If subobject was cached previously, use the cached one
 					var cached = scope.subobjectCache[ subobject.originalFileName.toLowerCase() ];
@@ -485,22 +766,6 @@ THREE.LDrawLoader = ( function () {
 
 					}
 
-					// Add the subobject just loaded
-					addSubobject( subobject, subobjectGroup );
-
-				}
-
-				function addSubobject( subobject, subobjectGroup ) {
-
-					if ( scope.separateObjects ) {
-
-						subobjectGroup.name = subobject.fileName;
-						objGroup.add( subobjectGroup );
-						subobjectGroup.matrix.copy( subobject.matrix );
-						subobjectGroup.matrixAutoUpdate = false;
-
-					}
-
 					scope.fileMap[ subobject.originalFileName ] = subobject.url;
 
 				}
@@ -536,8 +801,6 @@ THREE.LDrawLoader = ( function () {
 
 			this.materials = materials;
 
-			this.currentGroupObject = null;
-
 			return this;
 
 		},
@@ -568,9 +831,6 @@ THREE.LDrawLoader = ( function () {
 			}
 
 			var topParseScope = this.getCurrentParseScope();
-
-			var parentParseScope = this.getParentParseScope();
-
 			var newParseScope = {
 
 				lib: matLib,
@@ -581,15 +841,22 @@ THREE.LDrawLoader = ( function () {
 				numSubobjects: 0,
 				subobjectIndex: 0,
 				inverted: false,
+				category: null,
+				keywords: null,
 
 				// Current subobject
 				currentFileName: null,
 				mainColourCode: topParseScope ? topParseScope.mainColourCode : '16',
 				mainEdgeColourCode: topParseScope ? topParseScope.mainEdgeColourCode : '24',
 				currentMatrix: new THREE.Matrix4(),
+				matrix: new THREE.Matrix4(),
 
 				// If false, it is a root material scope previous to parse
-				isFromParse: true
+				isFromParse: true,
+
+				triangles: null,
+				lineSegments: null,
+				conditionalSegments: null,
 			};
 
 			this.parseScopesStack.push( newParseScope );
@@ -835,29 +1102,20 @@ THREE.LDrawLoader = ( function () {
 			switch ( finishType ) {
 
 				case LDrawLoader.FINISH_TYPE_DEFAULT:
+
+					material = new THREE.MeshStandardMaterial( { color: colour, roughness: 0.3, envMapIntensity: 0.3, metalness: 0 } );
+					break;
+
 				case LDrawLoader.FINISH_TYPE_PEARLESCENT:
 
+					// Try to imitate pearlescency by setting the specular to the complementary of the color, and low shininess
 					var specular = new THREE.Color( colour );
-					var shininess = 35;
 					var hsl = specular.getHSL( { h: 0, s: 0, l: 0 } );
-
-					if ( finishType === LDrawLoader.FINISH_TYPE_DEFAULT ) {
-
-						// Default plastic material with shiny specular
-						hsl.l = Math.min( 1, hsl.l + ( 1 - hsl.l ) * 0.12 );
-
-					} else {
-
-						// Try to imitate pearlescency by setting the specular to the complementary of the color, and low shininess
-						hsl.h = ( hsl.h + 0.5 ) % 1;
-						hsl.l = Math.min( 1, hsl.l + ( 1 - hsl.l ) * 0.7 );
-						shininess = 10;
-
-					}
-
+					hsl.h = ( hsl.h + 0.5 ) % 1;
+					hsl.l = Math.min( 1, hsl.l + ( 1 - hsl.l ) * 0.7 );
 					specular.setHSL( hsl.h, hsl.s, hsl.l );
 
-					material = new THREE.MeshPhongMaterial( { color: colour, specular: specular, shininess: shininess, reflectivity: 0.3 } );
+					material = new THREE.MeshPhongMaterial( { color: colour, specular: specular, shininess: 10, reflectivity: 0.3 } );
 					break;
 
 				case LDrawLoader.FINISH_TYPE_CHROME:
@@ -868,8 +1126,8 @@ THREE.LDrawLoader = ( function () {
 
 				case LDrawLoader.FINISH_TYPE_RUBBER:
 
-					// Rubber is best simulated with Lambert
-					material = new THREE.MeshLambertMaterial( { color: colour } );
+					// Rubber finish
+					material = new THREE.MeshStandardMaterial( { color: colour, roughness: 0.9, metalness: 0 } );
 					canHaveEnvMap = false;
 					break;
 
@@ -941,26 +1199,7 @@ THREE.LDrawLoader = ( function () {
 			// Parse result variables
 			var triangles;
 			var lineSegments;
-
-			if ( this.separateObjects ) {
-
-				triangles = [];
-				lineSegments = [];
-
-			} else {
-
-				if ( this.currentGroupObject === null ) {
-
-					this.currentGroupObject = new THREE.Group();
-					this.currentTriangles = [];
-					this.currentLineSegments = [];
-
-				}
-
-				triangles = this.currentTriangles;
-				lineSegments = this.currentLineSegments;
-
-			}
+			var conditionalSegments;
 
 			var subobjects = [];
 
@@ -982,6 +1221,12 @@ THREE.LDrawLoader = ( function () {
 			var currentEmbeddedFileName = null;
 			var currentEmbeddedText = null;
 
+			var bfcCertified = false;
+			var bfcCCW = true;
+			var bfcInverted = false;
+			var bfcCull = true;
+			var type = '';
+
 			var scope = this;
 			function parseColourCode( lineParser, forEdge ) {
 
@@ -1018,7 +1263,7 @@ THREE.LDrawLoader = ( function () {
 
 				if ( ! scope.separateObjects ) {
 
-					v.applyMatrix4( parentParseScope.currentMatrix );
+					v.applyMatrix4( currentParseScope.currentMatrix );
 
 				}
 
@@ -1026,11 +1271,6 @@ THREE.LDrawLoader = ( function () {
 
 			}
 
-			var bfcCertified = false;
-			var bfcCCW = true;
-			var bfcInverted = false;
-			var bfcCull = true;
-
 			// Parse all line commands
 			for ( lineIndex = 0; lineIndex < numLines; lineIndex ++ ) {
 
@@ -1085,6 +1325,45 @@ THREE.LDrawLoader = ( function () {
 
 							switch ( meta ) {
 
+								case '!LDRAW_ORG':
+
+									type = lp.getToken();
+
+									if ( ! parsingEmbeddedFiles ) {
+
+										currentParseScope.triangles = [];
+										currentParseScope.lineSegments = [];
+										currentParseScope.conditionalSegments = [];
+										currentParseScope.type = type;
+
+										var isRoot = ! parentParseScope.isFromParse;
+										if ( isRoot || scope.separateObjects && ! isPrimitiveType( type ) ) {
+
+											currentParseScope.groupObject = new THREE.Group();
+
+										}
+
+										// If the scale of the object is negated then the triangle winding order
+										// needs to be flipped.
+										var matrix = currentParseScope.matrix;
+										if (
+											matrix.determinant() < 0 && (
+												scope.separateObjects && isPrimitiveType( type ) ||
+												! scope.separateObjects
+											) ) {
+
+											currentParseScope.inverted = ! currentParseScope.inverted;
+
+										}
+
+										triangles = currentParseScope.triangles;
+										lineSegments = currentParseScope.lineSegments;
+										conditionalSegments = currentParseScope.conditionalSegments;
+
+									}
+
+									break;
+
 								case '!COLOUR':
 
 									var material = this.parseColourMetaDirective( lp );
@@ -1246,14 +1525,6 @@ THREE.LDrawLoader = ( function () {
 
 						}
 
-						// If the scale of the object is negated then the triangle winding order
-						// needs to be flipped.
-						if ( scope.separateObjects === false && matrix.determinant() < 0 ) {
-
-							bfcInverted = ! bfcInverted;
-
-						}
-
 						subobjects.push( {
 							material: material,
 							matrix: matrix,
@@ -1270,11 +1541,14 @@ THREE.LDrawLoader = ( function () {
 						break;
 
 					// Line type 2: Line segment
+					// Line type 5: Conditional Line segment
 					case '2':
+					case '5':
 
 						var material = parseColourCode( lp, true );
+						var arr = lineType === '2' ? lineSegments : conditionalSegments;
 
-						lineSegments.push( {
+						arr.push( {
 							material: material.userData.edgeMaterial,
 							colourCode: material.userData.code,
 							v0: parseVector( lp ),
@@ -1291,7 +1565,7 @@ THREE.LDrawLoader = ( function () {
 						var inverted = currentParseScope.inverted;
 						var ccw = bfcCCW !== inverted;
 						var doubleSided = ! bfcCertified || ! bfcCull;
-						var v0, v1, v2;
+						var v0, v1, v2, faceNormal;
 
 						if ( ccw === true ) {
 
@@ -1307,12 +1581,22 @@ THREE.LDrawLoader = ( function () {
 
 						}
 
+						tempVec0.subVectors( v1, v0 );
+						tempVec1.subVectors( v2, v1 );
+						faceNormal = new THREE.Vector3()
+							.crossVectors( tempVec0, tempVec1 )
+							.normalize();
+
 						triangles.push( {
 							material: material,
 							colourCode: material.userData.code,
 							v0: v0,
 							v1: v1,
-							v2: v2
+							v2: v2,
+							faceNormal: faceNormal,
+							n0: null,
+							n1: null,
+							n2: null
 						} );
 
 						if ( doubleSided === true ) {
@@ -1322,7 +1606,11 @@ THREE.LDrawLoader = ( function () {
 								colourCode: material.userData.code,
 								v0: v0,
 								v1: v2,
-								v2: v1
+								v2: v1,
+								faceNormal: faceNormal,
+								n0: null,
+								n1: null,
+								n2: null
 							} );
 
 						}
@@ -1337,7 +1625,7 @@ THREE.LDrawLoader = ( function () {
 						var inverted = currentParseScope.inverted;
 						var ccw = bfcCCW !== inverted;
 						var doubleSided = ! bfcCertified || ! bfcCull;
-						var v0, v1, v2, v3;
+						var v0, v1, v2, v3, faceNormal;
 
 						if ( ccw === true ) {
 
@@ -1355,12 +1643,22 @@ THREE.LDrawLoader = ( function () {
 
 						}
 
+						tempVec0.subVectors( v1, v0 );
+						tempVec1.subVectors( v2, v1 );
+						faceNormal = new THREE.Vector3()
+							.crossVectors( tempVec0, tempVec1 )
+							.normalize();
+
 						triangles.push( {
 							material: material,
 							colourCode: material.userData.code,
 							v0: v0,
 							v1: v1,
-							v2: v2
+							v2: v2,
+							faceNormal: faceNormal,
+							n0: null,
+							n1: null,
+							n2: null
 						} );
 
 						triangles.push( {
@@ -1368,7 +1666,11 @@ THREE.LDrawLoader = ( function () {
 							colourCode: material.userData.code,
 							v0: v0,
 							v1: v2,
-							v2: v3
+							v2: v3,
+							faceNormal: faceNormal,
+							n0: null,
+							n1: null,
+							n2: null
 						} );
 
 						if ( doubleSided === true ) {
@@ -1378,7 +1680,11 @@ THREE.LDrawLoader = ( function () {
 								colourCode: material.userData.code,
 								v0: v0,
 								v1: v2,
-								v2: v1
+								v2: v1,
+								faceNormal: faceNormal,
+								n0: null,
+								n1: null,
+								n2: null
 							} );
 
 							triangles.push( {
@@ -1386,18 +1692,17 @@ THREE.LDrawLoader = ( function () {
 								colourCode: material.userData.code,
 								v0: v0,
 								v1: v3,
-								v2: v2
+								v2: v2,
+								faceNormal: faceNormal,
+								n0: null,
+								n1: null,
+								n2: null
 							} );
 
 						}
 
 						break;
 
-					// Line type 5: Optional line
-					case '5':
-						// Line type 5 is not implemented
-						break;
-
 					default:
 						throw 'LDrawLoader: Unknown line type "' + lineType + '"' + lp.getLineNumberString() + '.';
 						break;
@@ -1412,40 +1717,11 @@ THREE.LDrawLoader = ( function () {
 
 			}
 
-			//
-
-			var groupObject = null;
-
-			if ( this.separateObjects ) {
-
-				groupObject = new THREE.Group();
-
-				if ( lineSegments.length > 0 ) {
-
-					groupObject.add( createObject( lineSegments, 2 ) );
-
-
-				}
-
-				if ( triangles.length > 0 ) {
-
-					groupObject.add( createObject( triangles, 3 ) );
-
-				}
-
-			} else {
-
-				groupObject = this.currentGroupObject;
-
-			}
-
-			groupObject.userData.category = category;
-			groupObject.userData.keywords = keywords;
-			groupObject.userData.subobjects = subobjects;
-
-			//console.timeEnd( 'LDrawLoader' );
-
-			return groupObject;
+			currentParseScope.category = category;
+			currentParseScope.keywords = keywords;
+			currentParseScope.subobjects = subobjects;
+			currentParseScope.numSubobjects = subobjects.length;
+			currentParseScope.subobjectIndex = 0;
 
 		}
 

+ 3 - 1
examples/js/loaders/MTLLoader.js

@@ -14,6 +14,8 @@ THREE.MTLLoader.prototype = {
 
 	constructor: THREE.MTLLoader,
 
+	crossOrigin: 'anonymous',
+
 	/**
 	 * Loads and parses a MTL asset from a URL.
 	 *
@@ -149,7 +151,7 @@ THREE.MTLLoader.prototype = {
 
 			} else {
 
-				if ( key === 'ka' || key === 'kd' || key === 'ks' || key ==='ke' ) {
+				if ( key === 'ka' || key === 'kd' || key === 'ks' || key === 'ke' ) {
 
 					var ss = value.split( delimiter_pattern, 3 );
 					info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ];

+ 169 - 82
examples/js/loaders/SVGLoader.js

@@ -136,7 +136,7 @@ THREE.SVGLoader.prototype = {
 
 		}
 
-		function parsePathNode( node, style ) {
+		function parsePathNode( node ) {
 
 			var path = new THREE.ShapePath();
 
@@ -161,8 +161,10 @@ THREE.SVGLoader.prototype = {
 				var data = command.substr( 1 ).trim();
 
 				if ( isFirstPoint === true ) {
+
 					doSetFirstPoint = true;
 					isFirstPoint = false;
+
 				}
 
 				switch ( type ) {
@@ -170,56 +172,78 @@ THREE.SVGLoader.prototype = {
 					case 'M':
 						var numbers = parseFloats( data );
 						for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) {
+
 							point.x = numbers[ j + 0 ];
 							point.y = numbers[ j + 1 ];
 							control.x = point.x;
 							control.y = point.y;
+
 							if ( j === 0 ) {
+
 								path.moveTo( point.x, point.y );
+
 							} else {
+
 								path.lineTo( point.x, point.y );
+
 							}
+
 							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
 						}
 						break;
 
 					case 'H':
 						var numbers = parseFloats( data );
+
 						for ( var j = 0, jl = numbers.length; j < jl; j ++ ) {
+
 							point.x = numbers[ j ];
 							control.x = point.x;
 							control.y = point.y;
 							path.lineTo( point.x, point.y );
+
 							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
 						}
 						break;
 
 					case 'V':
 						var numbers = parseFloats( data );
+
 						for ( var j = 0, jl = numbers.length; j < jl; j ++ ) {
+
 							point.y = numbers[ j ];
 							control.x = point.x;
 							control.y = point.y;
 							path.lineTo( point.x, point.y );
+
 							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
 						}
 						break;
 
 					case 'L':
 						var numbers = parseFloats( data );
+
 						for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) {
+
 							point.x = numbers[ j + 0 ];
 							point.y = numbers[ j + 1 ];
 							control.x = point.x;
 							control.y = point.y;
 							path.lineTo( point.x, point.y );
+
 							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
 						}
 						break;
 
 					case 'C':
 						var numbers = parseFloats( data );
+
 						for ( var j = 0, jl = numbers.length; j < jl; j += 6 ) {
+
 							path.bezierCurveTo(
 								numbers[ j + 0 ],
 								numbers[ j + 1 ],
@@ -232,13 +256,17 @@ THREE.SVGLoader.prototype = {
 							control.y = numbers[ j + 3 ];
 							point.x = numbers[ j + 4 ];
 							point.y = numbers[ j + 5 ];
+
 							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
 						}
 						break;
 
 					case 'S':
 						var numbers = parseFloats( data );
+
 						for ( var j = 0, jl = numbers.length; j < jl; j += 4 ) {
+
 							path.bezierCurveTo(
 								getReflection( point.x, control.x ),
 								getReflection( point.y, control.y ),
@@ -251,13 +279,17 @@ THREE.SVGLoader.prototype = {
 							control.y = numbers[ j + 1 ];
 							point.x = numbers[ j + 2 ];
 							point.y = numbers[ j + 3 ];
+
 							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
 						}
 						break;
 
 					case 'Q':
 						var numbers = parseFloats( data );
+
 						for ( var j = 0, jl = numbers.length; j < jl; j += 4 ) {
+
 							path.quadraticCurveTo(
 								numbers[ j + 0 ],
 								numbers[ j + 1 ],
@@ -268,13 +300,17 @@ THREE.SVGLoader.prototype = {
 							control.y = numbers[ j + 1 ];
 							point.x = numbers[ j + 2 ];
 							point.y = numbers[ j + 3 ];
+
 							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
 						}
 						break;
 
 					case 'T':
 						var numbers = parseFloats( data );
+
 						for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) {
+
 							var rx = getReflection( point.x, control.x );
 							var ry = getReflection( point.y, control.y );
 							path.quadraticCurveTo(
@@ -287,13 +323,17 @@ THREE.SVGLoader.prototype = {
 							control.y = ry;
 							point.x = numbers[ j + 0 ];
 							point.y = numbers[ j + 1 ];
+
 							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
 						}
 						break;
 
 					case 'A':
 						var numbers = parseFloats( data );
+
 						for ( var j = 0, jl = numbers.length; j < jl; j += 7 ) {
+
 							var start = point.clone();
 							point.x = numbers[ j + 5 ];
 							point.y = numbers[ j + 6 ];
@@ -302,65 +342,88 @@ THREE.SVGLoader.prototype = {
 							parseArcCommand(
 								path, numbers[ j ], numbers[ j + 1 ], numbers[ j + 2 ], numbers[ j + 3 ], numbers[ j + 4 ], start, point
 							);
+
 							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
 						}
 						break;
 
-					//
-
 					case 'm':
 						var numbers = parseFloats( data );
+
 						for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) {
+
 							point.x += numbers[ j + 0 ];
 							point.y += numbers[ j + 1 ];
 							control.x = point.x;
 							control.y = point.y;
+
 							if ( j === 0 ) {
+
 								path.moveTo( point.x, point.y );
+
 							} else {
+
 								path.lineTo( point.x, point.y );
+
 							}
+
 							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
 						}
 						break;
 
 					case 'h':
 						var numbers = parseFloats( data );
+
 						for ( var j = 0, jl = numbers.length; j < jl; j ++ ) {
+
 							point.x += numbers[ j ];
 							control.x = point.x;
 							control.y = point.y;
 							path.lineTo( point.x, point.y );
+
 							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
 						}
 						break;
 
 					case 'v':
 						var numbers = parseFloats( data );
+
 						for ( var j = 0, jl = numbers.length; j < jl; j ++ ) {
+
 							point.y += numbers[ j ];
 							control.x = point.x;
 							control.y = point.y;
 							path.lineTo( point.x, point.y );
+
 							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
 						}
 						break;
 
 					case 'l':
 						var numbers = parseFloats( data );
+
 						for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) {
+
 							point.x += numbers[ j + 0 ];
 							point.y += numbers[ j + 1 ];
 							control.x = point.x;
 							control.y = point.y;
 							path.lineTo( point.x, point.y );
+
 							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
 						}
 						break;
 
 					case 'c':
 						var numbers = parseFloats( data );
+
 						for ( var j = 0, jl = numbers.length; j < jl; j += 6 ) {
+
 							path.bezierCurveTo(
 								point.x + numbers[ j + 0 ],
 								point.y + numbers[ j + 1 ],
@@ -373,13 +436,17 @@ THREE.SVGLoader.prototype = {
 							control.y = point.y + numbers[ j + 3 ];
 							point.x += numbers[ j + 4 ];
 							point.y += numbers[ j + 5 ];
+
 							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
 						}
 						break;
 
 					case 's':
 						var numbers = parseFloats( data );
+
 						for ( var j = 0, jl = numbers.length; j < jl; j += 4 ) {
+
 							path.bezierCurveTo(
 								getReflection( point.x, control.x ),
 								getReflection( point.y, control.y ),
@@ -392,13 +459,17 @@ THREE.SVGLoader.prototype = {
 							control.y = point.y + numbers[ j + 1 ];
 							point.x += numbers[ j + 2 ];
 							point.y += numbers[ j + 3 ];
+
 							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
 						}
 						break;
 
 					case 'q':
 						var numbers = parseFloats( data );
+
 						for ( var j = 0, jl = numbers.length; j < jl; j += 4 ) {
+
 							path.quadraticCurveTo(
 								point.x + numbers[ j + 0 ],
 								point.y + numbers[ j + 1 ],
@@ -409,13 +480,17 @@ THREE.SVGLoader.prototype = {
 							control.y = point.y + numbers[ j + 1 ];
 							point.x += numbers[ j + 2 ];
 							point.y += numbers[ j + 3 ];
+
 							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
 						}
 						break;
 
 					case 't':
 						var numbers = parseFloats( data );
+
 						for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) {
+
 							var rx = getReflection( point.x, control.x );
 							var ry = getReflection( point.y, control.y );
 							path.quadraticCurveTo(
@@ -428,13 +503,17 @@ THREE.SVGLoader.prototype = {
 							control.y = ry;
 							point.x = point.x + numbers[ j + 0 ];
 							point.y = point.y + numbers[ j + 1 ];
+
 							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
 						}
 						break;
 
 					case 'a':
 						var numbers = parseFloats( data );
+
 						for ( var j = 0, jl = numbers.length; j < jl; j += 7 ) {
+
 							var start = point.clone();
 							point.x += numbers[ j + 5 ];
 							point.y += numbers[ j + 6 ];
@@ -443,20 +522,23 @@ THREE.SVGLoader.prototype = {
 							parseArcCommand(
 								path, numbers[ j ], numbers[ j + 1 ], numbers[ j + 2 ], numbers[ j + 3 ], numbers[ j + 4 ], start, point
 							);
+
 							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
 						}
 						break;
 
-					//
-
 					case 'Z':
 					case 'z':
 						path.currentPath.autoClose = true;
+
 						if ( path.currentPath.curves.length > 0 ) {
+
 							// Reset point to beginning of Path
 							point.copy( firstPoint );
 							path.currentPath.currentPoint.copy( point );
 							isFirstPoint = true;
+
 						}
 						break;
 
@@ -540,8 +622,8 @@ THREE.SVGLoader.prototype = {
 		function svgAngle( ux, uy, vx, vy ) {
 
 			var dot = ux * vx + uy * vy;
-			var len = Math.sqrt( ux * ux + uy * uy ) *  Math.sqrt( vx * vx + vy * vy );
-			var ang = Math.acos( Math.max( -1, Math.min( 1, dot / len ) ) ); // floating point precision, slightly over values appear
+			var len = Math.sqrt( ux * ux + uy * uy ) * Math.sqrt( vx * vx + vy * vy );
+			var ang = Math.acos( Math.max( - 1, Math.min( 1, dot / len ) ) ); // floating point precision, slightly over values appear
 			if ( ( ux * vy - uy * vx ) < 0 ) ang = - ang;
 			return ang;
 
@@ -551,7 +633,7 @@ THREE.SVGLoader.prototype = {
 		* According to https://www.w3.org/TR/SVG/shapes.html#RectElementRXAttribute
 		* rounded corner should be rendered to elliptical arc, but bezier curve does the job well enough
 		*/
-		function parseRectNode( node, style ) {
+		function parseRectNode( node ) {
 
 			var x = parseFloat( node.getAttribute( 'x' ) || 0 );
 			var y = parseFloat( node.getAttribute( 'y' ) || 0 );
@@ -586,7 +668,7 @@ THREE.SVGLoader.prototype = {
 
 		}
 
-		function parsePolygonNode( node, style ) {
+		function parsePolygonNode( node ) {
 
 			function iterator( match, a, b ) {
 
@@ -594,9 +676,13 @@ THREE.SVGLoader.prototype = {
 				var y = parseFloat( b );
 
 				if ( index === 0 ) {
+
 					path.moveTo( x, y );
+
 				} else {
+
 					path.lineTo( x, y );
+
 				}
 
 				index ++;
@@ -609,7 +695,7 @@ THREE.SVGLoader.prototype = {
 
 			var index = 0;
 
-			node.getAttribute( 'points' ).replace(regex, iterator);
+			node.getAttribute( 'points' ).replace( regex, iterator );
 
 			path.currentPath.autoClose = true;
 
@@ -617,7 +703,7 @@ THREE.SVGLoader.prototype = {
 
 		}
 
-		function parsePolylineNode( node, style ) {
+		function parsePolylineNode( node ) {
 
 			function iterator( match, a, b ) {
 
@@ -625,9 +711,13 @@ THREE.SVGLoader.prototype = {
 				var y = parseFloat( b );
 
 				if ( index === 0 ) {
+
 					path.moveTo( x, y );
+
 				} else {
+
 					path.lineTo( x, y );
+
 				}
 
 				index ++;
@@ -640,7 +730,7 @@ THREE.SVGLoader.prototype = {
 
 			var index = 0;
 
-			node.getAttribute( 'points' ).replace(regex, iterator);
+			node.getAttribute( 'points' ).replace( regex, iterator );
 
 			path.currentPath.autoClose = false;
 
@@ -648,7 +738,7 @@ THREE.SVGLoader.prototype = {
 
 		}
 
-		function parseCircleNode( node, style ) {
+		function parseCircleNode( node ) {
 
 			var x = parseFloat( node.getAttribute( 'cx' ) );
 			var y = parseFloat( node.getAttribute( 'cy' ) );
@@ -664,7 +754,7 @@ THREE.SVGLoader.prototype = {
 
 		}
 
-		function parseEllipseNode( node, style ) {
+		function parseEllipseNode( node ) {
 
 			var x = parseFloat( node.getAttribute( 'cx' ) );
 			var y = parseFloat( node.getAttribute( 'cy' ) );
@@ -681,7 +771,7 @@ THREE.SVGLoader.prototype = {
 
 		}
 
-		function parseLineNode( node, style ) {
+		function parseLineNode( node ) {
 
 			var x1 = parseFloat( node.getAttribute( 'x1' ) );
 			var y1 = parseFloat( node.getAttribute( 'y1' ) );
@@ -705,7 +795,11 @@ THREE.SVGLoader.prototype = {
 
 			function addStyle( svgName, jsName, adjustFunction ) {
 
-				if ( adjustFunction === undefined ) adjustFunction = function copy( v ) { return v; };
+				if ( adjustFunction === undefined ) adjustFunction = function copy( v ) {
+
+					return v;
+
+				};
 
 				if ( node.hasAttribute( svgName ) ) style[ jsName ] = adjustFunction( node.getAttribute( svgName ) );
 				if ( node.style[ svgName ] !== '' ) style[ jsName ] = adjustFunction( node.style[ svgName ] );
@@ -780,7 +874,9 @@ THREE.SVGLoader.prototype = {
 		function getNodeTransform( node ) {
 
 			if ( ! node.hasAttribute( 'transform' ) ) {
+
 				return null;
+
 			}
 
 			var transform = parseNodeTransform( node );
@@ -788,7 +884,9 @@ THREE.SVGLoader.prototype = {
 			if ( transform ) {
 
 				if ( transformStack.length > 0 ) {
+
 					transform.premultiply( transformStack[ transformStack.length - 1 ] );
+
 				}
 
 				currentTransform.copy( transform );
@@ -864,7 +962,7 @@ THREE.SVGLoader.prototype = {
 								}
 
 								// Rotate around center (cx, cy)
-								tempTransform1.identity().translate( -cx, -cy );
+								tempTransform1.identity().translate( - cx, - cy );
 								tempTransform2.identity().rotate( angle );
 								tempTransform3.multiplyMatrices( tempTransform2, tempTransform1 );
 								tempTransform1.identity().translate( cx, cy );
@@ -882,7 +980,9 @@ THREE.SVGLoader.prototype = {
 								var scaleY = scaleX;
 
 								if ( array.length >= 2 ) {
+
 									scaleY = array[ 1 ];
+
 								}
 
 								currentTransform.scale( scaleX, scaleY );
@@ -932,6 +1032,7 @@ THREE.SVGLoader.prototype = {
 							}
 
 							break;
+
 					}
 
 				}
@@ -958,12 +1059,12 @@ THREE.SVGLoader.prototype = {
 
 			var subPaths = path.subPaths;
 
-			for ( var i = 0, n = subPaths.length; i < n; i++ ) {
+			for ( var i = 0, n = subPaths.length; i < n; i ++ ) {
 
 				var subPath = subPaths[ i ];
 				var curves = subPath.curves;
 
-				for ( var j = 0; j < curves.length; j++ ) {
+				for ( var j = 0; j < curves.length; j ++ ) {
 
 					var curve = curves[ j ];
 
@@ -988,7 +1089,9 @@ THREE.SVGLoader.prototype = {
 					} else if ( curve.isEllipseCurve ) {
 
 						if ( isRotated ) {
+
 							console.warn( "SVGLoader: Elliptic arc or ellipse rotation or skewing is not implemented." );
+
 						}
 
 						tempV2.set( curve.aX, curve.aY );
@@ -1008,17 +1111,23 @@ THREE.SVGLoader.prototype = {
 		}
 
 		function isTransformRotated( m ) {
+
 			return m.elements[ 1 ] !== 0 || m.elements[ 3 ] !== 0;
+
 		}
 
 		function getTransformScaleX( m ) {
+
 			var te = m.elements;
-			return Math.sqrt( te[ 0 ] * te[ 0 ] + te[ 1 ] * te[ 1 ] )
+			return Math.sqrt( te[ 0 ] * te[ 0 ] + te[ 1 ] * te[ 1 ] );
+
 		}
 
 		function getTransformScaleY( m ) {
+
 			var te = m.elements;
-			return Math.sqrt( te[ 3 ] * te[ 3 ] + te[ 4 ] * te[ 4 ] )
+			return Math.sqrt( te[ 3 ] * te[ 3 ] + te[ 4 ] * te[ 4 ] );
+
 		}
 
 		//
@@ -1038,8 +1147,6 @@ THREE.SVGLoader.prototype = {
 
 		var currentTransform = new THREE.Matrix3();
 
-		var scope = this;
-
 		console.time( 'THREE.SVGLoader: DOMParser' );
 
 		var xml = new DOMParser().parseFromString( text, 'image/svg+xml' ); // application/xml
@@ -1071,7 +1178,7 @@ THREE.SVGLoader.prototype = {
 
 };
 
-THREE.SVGLoader.getStrokeStyle = function ( width, color, opacity, lineJoin, lineCap,  miterLimit ) {
+THREE.SVGLoader.getStrokeStyle = function ( width, color, opacity, lineJoin, lineCap, miterLimit ) {
 
 	// Param width: Stroke width
 	// Param color: As returned by THREE.Color.getStyle()
@@ -1136,7 +1243,6 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 	var tempV2_5 = new THREE.Vector2();
 	var tempV2_6 = new THREE.Vector2();
 	var tempV2_7 = new THREE.Vector2();
-	var tempV3_1 = new THREE.Vector3();
 	var lastPointL = new THREE.Vector2();
 	var lastPointR = new THREE.Vector2();
 	var point0L = new THREE.Vector2();
@@ -1147,9 +1253,6 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 	var nextPointR = new THREE.Vector2();
 	var innerPoint = new THREE.Vector2();
 	var outerPoint = new THREE.Vector2();
-	var tempTransform0 = new THREE.Matrix3();
-	var tempTransform1 = new THREE.Matrix3();
-	var tempTransform2 = new THREE.Matrix3();
 
 	return function ( points, style, arcDivisions, minDistance, vertices, normals, uvs, vertexOffset ) {
 
@@ -1160,7 +1263,7 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 		// if 'vertices' parameter is undefined no triangles will be generated, but the returned vertices count will still be valid (useful to preallocate the buffers)
 		// 'normals' and 'uvs' buffers are optional
 
-		arcLengthDivisions = arcDivisions !== undefined ? arcDivisions : 12;
+		arcDivisions = arcDivisions !== undefined ? arcDivisions : 12;
 		minDistance = minDistance !== undefined ? minDistance : 0.001;
 		vertexOffset = vertexOffset !== undefined ? vertexOffset : 0;
 
@@ -1210,11 +1313,9 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 					// Skip duplicated initial point
 					nextPoint = points[ 1 ];
 
-				}
-				else nextPoint = undefined;
+				} else nextPoint = undefined;
 
-			}
-			else {
+			} else {
 
 				nextPoint = points[ iPoint + 1 ];
 
@@ -1250,8 +1351,8 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 				}
 				if ( iPoint === 1 ) initialJoinIsOnLeftSide = joinIsOnLeftSide;
 
-				tempV2_3.subVectors( nextPoint, currentPoint )
-				var maxInnerDistance = tempV2_3.normalize();
+				tempV2_3.subVectors( nextPoint, currentPoint );
+				tempV2_3.normalize();
 				var dot = Math.abs( normal1.dot( tempV2_3 ) );
 
 				// If path is straight, don't create join
@@ -1287,16 +1388,14 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 							nextPointR.copy( innerPoint );
 							currentPointR.copy( innerPoint );
 
-						}
-						else {
+						} else {
 
 							nextPointL.copy( innerPoint );
 							currentPointL.copy( innerPoint );
 
 						}
 
-					}
-					else {
+					} else {
 
 						// The segment triangles are generated here if there was overlapping
 
@@ -1324,8 +1423,7 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 
 								makeCircularSector( currentPoint, currentPointL, nextPointL, u1, 0 );
 
-							}
-							else {
+							} else {
 
 								makeCircularSector( currentPoint, nextPointR, currentPointR, u1, 1 );
 
@@ -1348,8 +1446,7 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 									makeSegmentWithBevelJoin( joinIsOnLeftSide, innerSideModified, u1 );
 									break;
 
-								}
-								else {
+								} else {
 
 									// Segment triangles
 
@@ -1374,8 +1471,7 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 										addVertex( tempV2_7, u1, 0 );
 										addVertex( nextPointL, u1, 0 );
 
-									}
-									else {
+									} else {
 
 										tempV2_6.subVectors( outerPoint, currentPointR ).multiplyScalar( miterFraction ).add( currentPointR );
 										tempV2_7.subVectors( outerPoint, nextPointR ).multiplyScalar( miterFraction ).add( nextPointR );
@@ -1396,8 +1492,7 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 
 								}
 
-							}
-							else {
+							} else {
 
 								// Miter join segment triangles
 
@@ -1415,8 +1510,7 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 										addVertex( outerPoint, u1, 0 );
 										addVertex( innerPoint, u1, 1 );
 
-									}
-									else {
+									} else {
 
 										addVertex( lastPointR, u0, 1 );
 										addVertex( lastPointL, u0, 0 );
@@ -1433,16 +1527,14 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 
 										nextPointL.copy( outerPoint );
 
-									}
-									else {
+									} else {
 
 										nextPointR.copy( outerPoint );
 
 									}
 
 
-								}
-								else {
+								} else {
 
 									// Add extra miter join triangles
 
@@ -1456,8 +1548,7 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 										addVertex( outerPoint, u1, 0 );
 										addVertex( nextPointL, u1, 0 );
 
-									}
-									else {
+									} else {
 
 										addVertex( currentPointR, u1, 1 );
 										addVertex( outerPoint, u1, 1 );
@@ -1479,8 +1570,7 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 
 					}
 
-				}
-				else {
+				} else {
 
 					// The segment triangles are generated here when two consecutive points are collinear
 
@@ -1488,8 +1578,7 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 
 				}
 
-			}
-			else {
+			} else {
 
 				// The segment triangles are generated here if it is the ending segment
 
@@ -1520,16 +1609,18 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 			// Ending line endcap
 			addCapGeometry( currentPoint, currentPointL, currentPointR, joinIsOnLeftSide, false, u1 );
 
-		}
-		else if ( innerSideModified && vertices ) {
+		} else if ( innerSideModified && vertices ) {
 
 			// Modify path first segment vertices to adjust to the segments inner and outer intersections
 
 			var lastOuter = outerPoint;
 			var lastInner = innerPoint;
-			if ( initialJoinIsOnLeftSide !== joinIsOnLeftSide) {
+
+			if ( initialJoinIsOnLeftSide !== joinIsOnLeftSide ) {
+
 				lastOuter = innerPoint;
 				lastInner = outerPoint;
+
 			}
 
 			if ( joinIsOnLeftSide ) {
@@ -1540,10 +1631,10 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 				if ( isMiter ) {
 
 					lastOuter.toArray( vertices, 1 * 3 );
+
 				}
 
-			}
-			else {
+			} else {
 
 				lastInner.toArray( vertices, 1 * 3 );
 				lastInner.toArray( vertices, 3 * 3 );
@@ -1551,6 +1642,7 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 				if ( isMiter ) {
 
 					lastOuter.toArray( vertices, 0 * 3 );
+
 				}
 
 			}
@@ -1615,11 +1707,11 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 			var dot = tempV2_1.dot( tempV2_2 );
 			if ( Math.abs( dot ) < 1 ) angle = Math.abs( Math.acos( dot ) );
 
-			angle /= arcLengthDivisions;
+			angle /= arcDivisions;
 
 			tempV2_3.copy( p1 );
 
-			for ( var i = 0, il = arcLengthDivisions - 1; i < il; i++ ) {
+			for ( var i = 0, il = arcDivisions - 1; i < il; i ++ ) {
 
 				tempV2_4.copy( tempV2_3 ).rotateAround( center, angle );
 
@@ -1628,6 +1720,7 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 				addVertex( center, u, 0.5 );
 
 				tempV2_3.copy( tempV2_4 );
+
 			}
 
 			addVertex( tempV2_4, u, v );
@@ -1672,8 +1765,7 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 					addVertex( nextPointL, u, 0 );
 					addVertex( innerPoint, u, 0.5 );
 
-				}
-				else {
+				} else {
 
 					// Path segments triangles
 
@@ -1693,8 +1785,7 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 
 				}
 
-			}
-			else {
+			} else {
 
 				// Bevel join triangle. The segment triangles are done in the main loop
 
@@ -1704,8 +1795,7 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 					addVertex( nextPointL, u, 0 );
 					addVertex( currentPoint, u, 0.5 );
 
-				}
-				else {
+				} else {
 
 					addVertex( currentPointR, u, 1 );
 					addVertex( nextPointR, u, 0 );
@@ -1739,8 +1829,7 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 					addVertex( nextPointL, u0, 0 );
 					addVertex( innerPoint, u1, 1 );
 
-				}
-				else {
+				} else {
 
 					addVertex( lastPointR, u0, 1 );
 					addVertex( lastPointL, u0, 0 );
@@ -1777,8 +1866,7 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 
 						makeCircularSector( center, p2, p1, u, 0.5 );
 
-					}
-					else {
+					} else {
 
 						makeCircularSector( center, p1, p2, u, 0.5 );
 
@@ -1803,8 +1891,7 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 							tempV2_4.toArray( vertices, 0 * 3 );
 							tempV2_4.toArray( vertices, 3 * 3 );
 
-						}
-						else {
+						} else {
 
 							tempV2_3.toArray( vertices, 1 * 3 );
 							tempV2_3.toArray( vertices, 3 * 3 );
@@ -1812,8 +1899,7 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 
 						}
 
-					}
-					else {
+					} else {
 
 						tempV2_1.subVectors( p2, center );
 						tempV2_2.set( tempV2_1.y, - tempV2_1.x );
@@ -1830,8 +1916,7 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 							tempV2_4.toArray( vertices, vl - 2 * 3 );
 							tempV2_4.toArray( vertices, vl - 4 * 3 );
 
-						}
-						else {
+						} else {
 
 							tempV2_3.toArray( vertices, vl - 2 * 3 );
 							tempV2_4.toArray( vertices, vl - 1 * 3 );
@@ -1882,6 +1967,7 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 					newPoints.push( points[ i ] );
 
 				}
+
 			}
 
 			newPoints.push( points[ points.length - 1 ] );
@@ -1889,6 +1975,7 @@ THREE.SVGLoader.pointsToStrokeWithBuffers = function () {
 			return newPoints;
 
 		}
+
 	};
 
 }();

+ 1 - 1
examples/js/vr/WebVR.js

@@ -131,7 +131,7 @@ var WEBVR = {
 
 		}
 
-		if ( 'xr' in navigator ) {
+		if ( 'xr' in navigator && 'requestDevice' in navigator.xr ) {
 
 			var button = document.createElement( 'button' );
 			button.style.display = 'none';

+ 24 - 0
examples/jsm/controls/DeviceOrientationControls.d.ts

@@ -0,0 +1,24 @@
+import {
+  Camera,
+  Vector3
+} from '../../../src/Three';
+
+export class DeviceOrientationControls {
+  constructor(object: Camera);
+
+  object: Camera;
+
+  // API
+
+  alphaOffset: number;
+  deviceOrientation: any;
+  enabled: boolean;
+  screenOrientation: number;
+  target: Vector3;
+
+  connect(): void;
+  disconnect(): void;
+  dispose(): void;
+  update(): void;
+
+}

+ 120 - 0
examples/jsm/controls/DeviceOrientationControls.js

@@ -0,0 +1,120 @@
+/**
+ * @author richt / http://richt.me
+ * @author WestLangley / http://github.com/WestLangley
+ *
+ * W3C Device Orientation control (http://w3c.github.io/deviceorientation/spec-source-orientation.html)
+ */
+
+import {
+	Euler,
+	Math as _Math,
+	Quaternion,
+	Vector3
+} from "../../../build/three.module.js";
+
+var DeviceOrientationControls = function ( object ) {
+
+	var scope = this;
+
+	this.object = object;
+	this.object.rotation.reorder( 'YXZ' );
+
+	this.enabled = true;
+
+	this.deviceOrientation = {};
+	this.screenOrientation = 0;
+
+	this.alphaOffset = 0; // radians
+
+	var onDeviceOrientationChangeEvent = function ( event ) {
+
+		scope.deviceOrientation = event;
+
+	};
+
+	var onScreenOrientationChangeEvent = function () {
+
+		scope.screenOrientation = window.orientation || 0;
+
+	};
+
+	// The angles alpha, beta and gamma form a set of intrinsic Tait-Bryan angles of type Z-X'-Y''
+
+	var setObjectQuaternion = function () {
+
+		var zee = new Vector3( 0, 0, 1 );
+
+		var euler = new Euler();
+
+		var q0 = new Quaternion();
+
+		var q1 = new Quaternion( - Math.sqrt( 0.5 ), 0, 0, Math.sqrt( 0.5 ) ); // - PI/2 around the x-axis
+
+		return function ( quaternion, alpha, beta, gamma, orient ) {
+
+			euler.set( beta, alpha, - gamma, 'YXZ' ); // 'ZXY' for the device, but 'YXZ' for us
+
+			quaternion.setFromEuler( euler ); // orient the device
+
+			quaternion.multiply( q1 ); // camera looks out the back of the device, not the top
+
+			quaternion.multiply( q0.setFromAxisAngle( zee, - orient ) ); // adjust for screen orientation
+
+		};
+
+	}();
+
+	this.connect = function () {
+
+		onScreenOrientationChangeEvent(); // run once on load
+
+		window.addEventListener( 'orientationchange', onScreenOrientationChangeEvent, false );
+		window.addEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, false );
+
+		scope.enabled = true;
+
+	};
+
+	this.disconnect = function () {
+
+		window.removeEventListener( 'orientationchange', onScreenOrientationChangeEvent, false );
+		window.removeEventListener( 'deviceorientation', onDeviceOrientationChangeEvent, false );
+
+		scope.enabled = false;
+
+	};
+
+	this.update = function () {
+
+		if ( scope.enabled === false ) return;
+
+		var device = scope.deviceOrientation;
+
+		if ( device ) {
+
+			var alpha = device.alpha ? _Math.degToRad( device.alpha ) + scope.alphaOffset : 0; // Z
+
+			var beta = device.beta ? _Math.degToRad( device.beta ) : 0; // X'
+
+			var gamma = device.gamma ? _Math.degToRad( device.gamma ) : 0; // Y''
+
+			var orient = scope.screenOrientation ? _Math.degToRad( scope.screenOrientation ) : 0; // O
+
+			setObjectQuaternion( scope.object.quaternion, alpha, beta, gamma, orient );
+
+		}
+
+
+	};
+
+	this.dispose = function () {
+
+		scope.disconnect();
+
+	};
+
+	this.connect();
+
+};
+
+export { DeviceOrientationControls };

+ 20 - 0
examples/jsm/controls/DragControls.d.ts

@@ -0,0 +1,20 @@
+import {
+  Camera,
+  EventDispatcher,
+  Object3D
+} from '../../../src/Three';
+
+export class DragControls extends EventDispatcher {
+  constructor(objects: Object3D[], camera: Camera, domElement?: HTMLElement);
+
+  object: Camera;
+
+  // API
+
+  enabled: boolean;
+
+  activate(): void;
+  deactivate(): void;
+  dispose(): void;
+
+}

+ 298 - 0
examples/jsm/controls/DragControls.js

@@ -0,0 +1,298 @@
+/*
+ * @author zz85 / https://github.com/zz85
+ * @author mrdoob / http://mrdoob.com
+ * Running this will allow you to drag three.js objects around the screen.
+ */
+
+import {
+	Camera,
+	EventDispatcher,
+	Matrix4,
+	Plane,
+	Raycaster,
+	Vector2,
+	Vector3
+} from "../../../build/three.module.js";
+
+var DragControls = function ( _objects, _camera, _domElement ) {
+
+	if ( _objects instanceof Camera ) {
+
+		console.warn( 'THREE.DragControls: Constructor now expects ( objects, camera, domElement )' );
+		var temp = _objects; _objects = _camera; _camera = temp;
+
+	}
+
+	var _plane = new Plane();
+	var _raycaster = new Raycaster();
+
+	var _mouse = new Vector2();
+	var _offset = new Vector3();
+	var _intersection = new Vector3();
+	var _worldPosition = new Vector3();
+	var _inverseMatrix = new Matrix4();
+
+	var _selected = null, _hovered = null;
+
+	//
+
+	var scope = this;
+
+	function activate() {
+
+		_domElement.addEventListener( 'mousemove', onDocumentMouseMove, false );
+		_domElement.addEventListener( 'mousedown', onDocumentMouseDown, false );
+		_domElement.addEventListener( 'mouseup', onDocumentMouseCancel, false );
+		_domElement.addEventListener( 'mouseleave', onDocumentMouseCancel, false );
+		_domElement.addEventListener( 'touchmove', onDocumentTouchMove, false );
+		_domElement.addEventListener( 'touchstart', onDocumentTouchStart, false );
+		_domElement.addEventListener( 'touchend', onDocumentTouchEnd, false );
+
+	}
+
+	function deactivate() {
+
+		_domElement.removeEventListener( 'mousemove', onDocumentMouseMove, false );
+		_domElement.removeEventListener( 'mousedown', onDocumentMouseDown, false );
+		_domElement.removeEventListener( 'mouseup', onDocumentMouseCancel, false );
+		_domElement.removeEventListener( 'mouseleave', onDocumentMouseCancel, false );
+		_domElement.removeEventListener( 'touchmove', onDocumentTouchMove, false );
+		_domElement.removeEventListener( 'touchstart', onDocumentTouchStart, false );
+		_domElement.removeEventListener( 'touchend', onDocumentTouchEnd, false );
+
+	}
+
+	function dispose() {
+
+		deactivate();
+
+	}
+
+	function onDocumentMouseMove( event ) {
+
+		event.preventDefault();
+
+		var rect = _domElement.getBoundingClientRect();
+
+		_mouse.x = ( ( event.clientX - rect.left ) / rect.width ) * 2 - 1;
+		_mouse.y = - ( ( event.clientY - rect.top ) / rect.height ) * 2 + 1;
+
+		_raycaster.setFromCamera( _mouse, _camera );
+
+		if ( _selected && scope.enabled ) {
+
+			if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {
+
+				_selected.position.copy( _intersection.sub( _offset ).applyMatrix4( _inverseMatrix ) );
+
+			}
+
+			scope.dispatchEvent( { type: 'drag', object: _selected } );
+
+			return;
+
+		}
+
+		_raycaster.setFromCamera( _mouse, _camera );
+
+		var intersects = _raycaster.intersectObjects( _objects );
+
+		if ( intersects.length > 0 ) {
+
+			var object = intersects[ 0 ].object;
+
+			_plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( object.matrixWorld ) );
+
+			if ( _hovered !== object ) {
+
+				scope.dispatchEvent( { type: 'hoveron', object: object } );
+
+				_domElement.style.cursor = 'pointer';
+				_hovered = object;
+
+			}
+
+		} else {
+
+			if ( _hovered !== null ) {
+
+				scope.dispatchEvent( { type: 'hoveroff', object: _hovered } );
+
+				_domElement.style.cursor = 'auto';
+				_hovered = null;
+
+			}
+
+		}
+
+	}
+
+	function onDocumentMouseDown( event ) {
+
+		event.preventDefault();
+
+		_raycaster.setFromCamera( _mouse, _camera );
+
+		var intersects = _raycaster.intersectObjects( _objects );
+
+		if ( intersects.length > 0 ) {
+
+			_selected = intersects[ 0 ].object;
+
+			if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {
+
+				_inverseMatrix.getInverse( _selected.parent.matrixWorld );
+				_offset.copy( _intersection ).sub( _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
+
+			}
+
+			_domElement.style.cursor = 'move';
+
+			scope.dispatchEvent( { type: 'dragstart', object: _selected } );
+
+		}
+
+
+	}
+
+	function onDocumentMouseCancel( event ) {
+
+		event.preventDefault();
+
+		if ( _selected ) {
+
+			scope.dispatchEvent( { type: 'dragend', object: _selected } );
+
+			_selected = null;
+
+		}
+
+		_domElement.style.cursor = _hovered ? 'pointer' : 'auto';
+
+	}
+
+	function onDocumentTouchMove( event ) {
+
+		event.preventDefault();
+		event = event.changedTouches[ 0 ];
+
+		var rect = _domElement.getBoundingClientRect();
+
+		_mouse.x = ( ( event.clientX - rect.left ) / rect.width ) * 2 - 1;
+		_mouse.y = - ( ( event.clientY - rect.top ) / rect.height ) * 2 + 1;
+
+		_raycaster.setFromCamera( _mouse, _camera );
+
+		if ( _selected && scope.enabled ) {
+
+			if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {
+
+				_selected.position.copy( _intersection.sub( _offset ).applyMatrix4( _inverseMatrix ) );
+
+			}
+
+			scope.dispatchEvent( { type: 'drag', object: _selected } );
+
+			return;
+
+		}
+
+	}
+
+	function onDocumentTouchStart( event ) {
+
+		event.preventDefault();
+		event = event.changedTouches[ 0 ];
+
+		var rect = _domElement.getBoundingClientRect();
+
+		_mouse.x = ( ( event.clientX - rect.left ) / rect.width ) * 2 - 1;
+		_mouse.y = - ( ( event.clientY - rect.top ) / rect.height ) * 2 + 1;
+
+		_raycaster.setFromCamera( _mouse, _camera );
+
+		var intersects = _raycaster.intersectObjects( _objects );
+
+		if ( intersects.length > 0 ) {
+
+			_selected = intersects[ 0 ].object;
+
+			_plane.setFromNormalAndCoplanarPoint( _camera.getWorldDirection( _plane.normal ), _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
+
+			if ( _raycaster.ray.intersectPlane( _plane, _intersection ) ) {
+
+				_inverseMatrix.getInverse( _selected.parent.matrixWorld );
+				_offset.copy( _intersection ).sub( _worldPosition.setFromMatrixPosition( _selected.matrixWorld ) );
+
+			}
+
+			_domElement.style.cursor = 'move';
+
+			scope.dispatchEvent( { type: 'dragstart', object: _selected } );
+
+		}
+
+
+	}
+
+	function onDocumentTouchEnd( event ) {
+
+		event.preventDefault();
+
+		if ( _selected ) {
+
+			scope.dispatchEvent( { type: 'dragend', object: _selected } );
+
+			_selected = null;
+
+		}
+
+		_domElement.style.cursor = 'auto';
+
+	}
+
+	activate();
+
+	// API
+
+	this.enabled = true;
+
+	this.activate = activate;
+	this.deactivate = deactivate;
+	this.dispose = dispose;
+
+	// Backward compatibility
+
+	this.setObjects = function () {
+
+		console.error( 'THREE.DragControls: setObjects() has been removed.' );
+
+	};
+
+	this.on = function ( type, listener ) {
+
+		console.warn( 'THREE.DragControls: on() has been deprecated. Use addEventListener() instead.' );
+		scope.addEventListener( type, listener );
+
+	};
+
+	this.off = function ( type, listener ) {
+
+		console.warn( 'THREE.DragControls: off() has been deprecated. Use removeEventListener() instead.' );
+		scope.removeEventListener( type, listener );
+
+	};
+
+	this.notify = function ( type ) {
+
+		console.error( 'THREE.DragControls: notify() has been deprecated. Use dispatchEvent() instead.' );
+		scope.dispatchEvent( { type: type } );
+
+	};
+
+};
+
+DragControls.prototype = Object.create( EventDispatcher.prototype );
+DragControls.prototype.constructor = DragControls;
+
+export { DragControls };

+ 26 - 0
examples/jsm/controls/EditorControls.d.ts

@@ -0,0 +1,26 @@
+import {
+  Camera,
+  EventDispatcher,
+  Vector3,
+  Object3D
+} from '../../../src/Three';
+
+export class EditorControls extends EventDispatcher {
+  constructor(object: Camera, domElement?: HTMLElement);
+
+  object: Camera;
+  domElement: HTMLElement | HTMLDocument;
+
+  enabled: boolean;
+  center: Vector3;
+  panSpeed: number;
+  zoomSpeed: number;
+  rotationSpeed: number;
+
+  focus(target: Object3D): void;
+  pan(delta: Vector3): void;
+  zoom(delta: Vector3): void;
+  rotate(delta: Vector3): void;
+  dispose(): void;
+
+}

+ 327 - 0
examples/jsm/controls/EditorControls.js

@@ -0,0 +1,327 @@
+/**
+ * @author qiao / https://github.com/qiao
+ * @author mrdoob / http://mrdoob.com
+ * @author alteredq / http://alteredqualia.com/
+ * @author WestLangley / http://github.com/WestLangley
+ */
+
+import {
+	Box3,
+	EventDispatcher,
+	Matrix3,
+	Sphere,
+	Spherical,
+	Vector2,
+	Vector3
+} from "../../../build/three.module.js";
+
+var EditorControls = function ( object, domElement ) {
+
+	domElement = ( domElement !== undefined ) ? domElement : document;
+
+	// API
+
+	this.enabled = true;
+	this.center = new Vector3();
+	this.panSpeed = 0.002;
+	this.zoomSpeed = 0.1;
+	this.rotationSpeed = 0.005;
+
+	// internals
+
+	var scope = this;
+	var vector = new Vector3();
+	var delta = new Vector3();
+	var box = new Box3();
+
+	var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2 };
+	var state = STATE.NONE;
+
+	var center = this.center;
+	var normalMatrix = new Matrix3();
+	var pointer = new Vector2();
+	var pointerOld = new Vector2();
+	var spherical = new Spherical();
+	var sphere = new Sphere();
+
+	// events
+
+	var changeEvent = { type: 'change' };
+
+	this.focus = function ( target ) {
+
+		var distance;
+
+		box.setFromObject( target );
+
+		if ( box.isEmpty() === false ) {
+
+			box.getCenter( center );
+			distance = box.getBoundingSphere( sphere ).radius;
+
+		} else {
+
+			// Focusing on an Group, AmbientLight, etc
+
+			center.setFromMatrixPosition( target.matrixWorld );
+			distance = 0.1;
+
+		}
+
+		delta.set( 0, 0, 1 );
+		delta.applyQuaternion( object.quaternion );
+		delta.multiplyScalar( distance * 4 );
+
+		object.position.copy( center ).add( delta );
+
+		scope.dispatchEvent( changeEvent );
+
+	};
+
+	this.pan = function ( delta ) {
+
+		var distance = object.position.distanceTo( center );
+
+		delta.multiplyScalar( distance * scope.panSpeed );
+		delta.applyMatrix3( normalMatrix.getNormalMatrix( object.matrix ) );
+
+		object.position.add( delta );
+		center.add( delta );
+
+		scope.dispatchEvent( changeEvent );
+
+	};
+
+	this.zoom = function ( delta ) {
+
+		var distance = object.position.distanceTo( center );
+
+		delta.multiplyScalar( distance * scope.zoomSpeed );
+
+		if ( delta.length() > distance ) return;
+
+		delta.applyMatrix3( normalMatrix.getNormalMatrix( object.matrix ) );
+
+		object.position.add( delta );
+
+		scope.dispatchEvent( changeEvent );
+
+	};
+
+	this.rotate = function ( delta ) {
+
+		vector.copy( object.position ).sub( center );
+
+		spherical.setFromVector3( vector );
+
+		spherical.theta += delta.x * scope.rotationSpeed;
+		spherical.phi += delta.y * scope.rotationSpeed;
+
+		spherical.makeSafe();
+
+		vector.setFromSpherical( spherical );
+
+		object.position.copy( center ).add( vector );
+
+		object.lookAt( center );
+
+		scope.dispatchEvent( changeEvent );
+
+	};
+
+	// mouse
+
+	function onMouseDown( event ) {
+
+		if ( scope.enabled === false ) return;
+
+		if ( event.button === 0 ) {
+
+			state = STATE.ROTATE;
+
+		} else if ( event.button === 1 ) {
+
+			state = STATE.ZOOM;
+
+		} else if ( event.button === 2 ) {
+
+			state = STATE.PAN;
+
+		}
+
+		pointerOld.set( event.clientX, event.clientY );
+
+		domElement.addEventListener( 'mousemove', onMouseMove, false );
+		domElement.addEventListener( 'mouseup', onMouseUp, false );
+		domElement.addEventListener( 'mouseout', onMouseUp, false );
+		domElement.addEventListener( 'dblclick', onMouseUp, false );
+
+	}
+
+	function onMouseMove( event ) {
+
+		if ( scope.enabled === false ) return;
+
+		pointer.set( event.clientX, event.clientY );
+
+		var movementX = pointer.x - pointerOld.x;
+		var movementY = pointer.y - pointerOld.y;
+
+		if ( state === STATE.ROTATE ) {
+
+			scope.rotate( delta.set( - movementX, - movementY, 0 ) );
+
+		} else if ( state === STATE.ZOOM ) {
+
+			scope.zoom( delta.set( 0, 0, movementY ) );
+
+		} else if ( state === STATE.PAN ) {
+
+			scope.pan( delta.set( - movementX, movementY, 0 ) );
+
+		}
+
+		pointerOld.set( event.clientX, event.clientY );
+
+	}
+
+	function onMouseUp( event ) {
+
+		domElement.removeEventListener( 'mousemove', onMouseMove, false );
+		domElement.removeEventListener( 'mouseup', onMouseUp, false );
+		domElement.removeEventListener( 'mouseout', onMouseUp, false );
+		domElement.removeEventListener( 'dblclick', onMouseUp, false );
+
+		state = STATE.NONE;
+
+	}
+
+	function onMouseWheel( event ) {
+
+		event.preventDefault();
+
+		// Normalize deltaY due to https://bugzilla.mozilla.org/show_bug.cgi?id=1392460
+		scope.zoom( delta.set( 0, 0, event.deltaY > 0 ? 1 : - 1 ) );
+
+	}
+
+	function contextmenu( event ) {
+
+		event.preventDefault();
+
+	}
+
+	this.dispose = function () {
+
+		domElement.removeEventListener( 'contextmenu', contextmenu, false );
+		domElement.removeEventListener( 'mousedown', onMouseDown, false );
+		domElement.removeEventListener( 'wheel', onMouseWheel, false );
+
+		domElement.removeEventListener( 'mousemove', onMouseMove, false );
+		domElement.removeEventListener( 'mouseup', onMouseUp, false );
+		domElement.removeEventListener( 'mouseout', onMouseUp, false );
+		domElement.removeEventListener( 'dblclick', onMouseUp, false );
+
+		domElement.removeEventListener( 'touchstart', touchStart, false );
+		domElement.removeEventListener( 'touchmove', touchMove, false );
+
+	};
+
+	domElement.addEventListener( 'contextmenu', contextmenu, false );
+	domElement.addEventListener( 'mousedown', onMouseDown, false );
+	domElement.addEventListener( 'wheel', onMouseWheel, false );
+
+	// touch
+
+	var touches = [ new Vector3(), new Vector3(), new Vector3() ];
+	var prevTouches = [ new Vector3(), new Vector3(), new Vector3() ];
+
+	var prevDistance = null;
+
+	function touchStart( event ) {
+
+		if ( scope.enabled === false ) return;
+
+		switch ( event.touches.length ) {
+
+			case 1:
+				touches[ 0 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
+				touches[ 1 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
+				break;
+
+			case 2:
+				touches[ 0 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
+				touches[ 1 ].set( event.touches[ 1 ].pageX, event.touches[ 1 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
+				prevDistance = touches[ 0 ].distanceTo( touches[ 1 ] );
+				break;
+
+		}
+
+		prevTouches[ 0 ].copy( touches[ 0 ] );
+		prevTouches[ 1 ].copy( touches[ 1 ] );
+
+	}
+
+
+	function touchMove( event ) {
+
+		if ( scope.enabled === false ) return;
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		function getClosest( touch, touches ) {
+
+			var closest = touches[ 0 ];
+
+			for ( var i in touches ) {
+
+				if ( closest.distanceTo( touch ) > touches[ i ].distanceTo( touch ) ) closest = touches[ i ];
+
+			}
+
+			return closest;
+
+		}
+
+		switch ( event.touches.length ) {
+
+			case 1:
+				touches[ 0 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
+				touches[ 1 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
+				scope.rotate( touches[ 0 ].sub( getClosest( touches[ 0 ], prevTouches ) ).multiplyScalar( - 1 ) );
+				break;
+
+			case 2:
+				touches[ 0 ].set( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
+				touches[ 1 ].set( event.touches[ 1 ].pageX, event.touches[ 1 ].pageY, 0 ).divideScalar( window.devicePixelRatio );
+				var distance = touches[ 0 ].distanceTo( touches[ 1 ] );
+				scope.zoom( delta.set( 0, 0, prevDistance - distance ) );
+				prevDistance = distance;
+
+
+				var offset0 = touches[ 0 ].clone().sub( getClosest( touches[ 0 ], prevTouches ) );
+				var offset1 = touches[ 1 ].clone().sub( getClosest( touches[ 1 ], prevTouches ) );
+				offset0.x = - offset0.x;
+				offset1.x = - offset1.x;
+
+				scope.pan( offset0.add( offset1 ) );
+
+				break;
+
+		}
+
+		prevTouches[ 0 ].copy( touches[ 0 ] );
+		prevTouches[ 1 ].copy( touches[ 1 ] );
+
+	}
+
+	domElement.addEventListener( 'touchstart', touchStart, false );
+	domElement.addEventListener( 'touchmove', touchMove, false );
+
+};
+
+EditorControls.prototype = Object.create( EventDispatcher.prototype );
+EditorControls.prototype.constructor = EditorControls;
+
+export { EditorControls };

+ 32 - 0
examples/jsm/controls/FirstPersonControls.d.ts

@@ -0,0 +1,32 @@
+import {
+  Camera,
+  Vector3
+} from '../../../src/Three';
+
+export class FirstPersonControls {
+  constructor(object: Camera, domElement?: HTMLElement);
+
+  object: Camera;
+  domElement: HTMLElement | HTMLDocument;
+
+  enabled: boolean;
+  movementSpeed: number;
+  lookSpeed: number;
+  lookVertical: boolean;
+  autoForward: boolean;
+  activeLook: boolean;
+  heightSpeed: boolean;
+  heightCoef: number;
+  heightMin: number;
+  heightMax: number;
+  constrainVertical: boolean;
+  verticalMin: number;
+  verticalMax: number;
+  autoSpeedFactor: number;
+
+  handleResize(): void;
+  lookAt(x: number | Vector3, y: number, z: number): this;
+  update(delta: number): this;
+  dispose(): void;
+
+}

+ 350 - 0
examples/jsm/controls/FirstPersonControls.js

@@ -0,0 +1,350 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author alteredq / http://alteredqualia.com/
+ * @author paulirish / http://paulirish.com/
+ */
+
+import {
+	Math as _Math,
+	Spherical,
+	Vector3
+} from "../../../build/three.module.js";
+
+var FirstPersonControls = function ( object, domElement ) {
+
+	this.object = object;
+
+	this.domElement = ( domElement !== undefined ) ? domElement : document;
+
+	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.autoSpeedFactor = 0.0;
+
+	this.mouseX = 0;
+	this.mouseY = 0;
+
+	this.moveForward = false;
+	this.moveBackward = false;
+	this.moveLeft = false;
+	this.moveRight = false;
+
+	this.mouseDragOn = false;
+
+	this.viewHalfX = 0;
+	this.viewHalfY = 0;
+
+	// private variables
+
+	var lat = 0;
+	var lon = 0;
+
+	var lookDirection = new Vector3();
+	var spherical = new Spherical();
+	var target = new Vector3();
+
+	//
+
+	if ( this.domElement !== document ) {
+
+		this.domElement.setAttribute( 'tabindex', - 1 );
+
+	}
+
+	//
+
+	this.handleResize = function () {
+
+		if ( this.domElement === document ) {
+
+			this.viewHalfX = window.innerWidth / 2;
+			this.viewHalfY = window.innerHeight / 2;
+
+		} else {
+
+			this.viewHalfX = this.domElement.offsetWidth / 2;
+			this.viewHalfY = this.domElement.offsetHeight / 2;
+
+		}
+
+	};
+
+	this.onMouseDown = function ( event ) {
+
+		if ( this.domElement !== document ) {
+
+			this.domElement.focus();
+
+		}
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		if ( this.activeLook ) {
+
+			switch ( event.button ) {
+
+				case 0: this.moveForward = true; break;
+				case 2: this.moveBackward = true; break;
+
+			}
+
+		}
+
+		this.mouseDragOn = true;
+
+	};
+
+	this.onMouseUp = function ( event ) {
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		if ( this.activeLook ) {
+
+			switch ( event.button ) {
+
+				case 0: this.moveForward = false; break;
+				case 2: this.moveBackward = false; break;
+
+			}
+
+		}
+
+		this.mouseDragOn = false;
+
+	};
+
+	this.onMouseMove = function ( event ) {
+
+		if ( this.domElement === document ) {
+
+			this.mouseX = event.pageX - this.viewHalfX;
+			this.mouseY = event.pageY - this.viewHalfY;
+
+		} else {
+
+			this.mouseX = event.pageX - this.domElement.offsetLeft - this.viewHalfX;
+			this.mouseY = event.pageY - this.domElement.offsetTop - this.viewHalfY;
+
+		}
+
+	};
+
+	this.onKeyDown = function ( event ) {
+
+		//event.preventDefault();
+
+		switch ( event.keyCode ) {
+
+			case 38: /*up*/
+			case 87: /*W*/ this.moveForward = true; break;
+
+			case 37: /*left*/
+			case 65: /*A*/ this.moveLeft = true; break;
+
+			case 40: /*down*/
+			case 83: /*S*/ this.moveBackward = true; break;
+
+			case 39: /*right*/
+			case 68: /*D*/ this.moveRight = true; break;
+
+			case 82: /*R*/ this.moveUp = true; break;
+			case 70: /*F*/ this.moveDown = true; break;
+
+		}
+
+	};
+
+	this.onKeyUp = function ( event ) {
+
+		switch ( event.keyCode ) {
+
+			case 38: /*up*/
+			case 87: /*W*/ this.moveForward = false; break;
+
+			case 37: /*left*/
+			case 65: /*A*/ this.moveLeft = false; break;
+
+			case 40: /*down*/
+			case 83: /*S*/ this.moveBackward = false; break;
+
+			case 39: /*right*/
+			case 68: /*D*/ this.moveRight = false; break;
+
+			case 82: /*R*/ this.moveUp = false; break;
+			case 70: /*F*/ this.moveDown = false; break;
+
+		}
+
+	};
+
+	this.lookAt = function ( x, y, z ) {
+
+		if ( x.isVector3 ) {
+
+			target.copy( x );
+
+		} else {
+
+			target.set( x, y, z );
+
+		}
+
+		this.object.lookAt( target );
+
+		setOrientation( this );
+
+		return this;
+
+	};
+
+	this.update = function () {
+
+		var targetPosition = new Vector3();
+
+		return function update( delta ) {
+
+			if ( this.enabled === false ) return;
+
+			if ( this.heightSpeed ) {
+
+				var y = _Math.clamp( this.object.position.y, this.heightMin, this.heightMax );
+				var heightDelta = y - this.heightMin;
+
+				this.autoSpeedFactor = delta * ( heightDelta * this.heightCoef );
+
+			} else {
+
+				this.autoSpeedFactor = 0.0;
+
+			}
+
+			var actualMoveSpeed = delta * this.movementSpeed;
+
+			if ( this.moveForward || ( this.autoForward && ! this.moveBackward ) ) this.object.translateZ( - ( actualMoveSpeed + this.autoSpeedFactor ) );
+			if ( this.moveBackward ) this.object.translateZ( actualMoveSpeed );
+
+			if ( this.moveLeft ) this.object.translateX( - actualMoveSpeed );
+			if ( this.moveRight ) this.object.translateX( actualMoveSpeed );
+
+			if ( this.moveUp ) this.object.translateY( actualMoveSpeed );
+			if ( this.moveDown ) this.object.translateY( - actualMoveSpeed );
+
+			var actualLookSpeed = delta * this.lookSpeed;
+
+			if ( ! this.activeLook ) {
+
+				actualLookSpeed = 0;
+
+			}
+
+			var verticalLookRatio = 1;
+
+			if ( this.constrainVertical ) {
+
+				verticalLookRatio = Math.PI / ( this.verticalMax - this.verticalMin );
+
+			}
+
+			lon -= this.mouseX * actualLookSpeed;
+			if ( this.lookVertical ) lat -= this.mouseY * actualLookSpeed * verticalLookRatio;
+
+			lat = Math.max( - 85, Math.min( 85, lat ) );
+
+			var phi = _Math.degToRad( 90 - lat );
+			var theta = _Math.degToRad( lon );
+
+			if ( this.constrainVertical ) {
+
+				phi = _Math.mapLinear( phi, 0, Math.PI, this.verticalMin, this.verticalMax );
+
+			}
+
+			var position = this.object.position;
+
+			targetPosition.setFromSphericalCoords( 1, phi, theta ).add( position );
+
+			this.object.lookAt( targetPosition );
+
+		};
+
+	}();
+
+	function contextmenu( event ) {
+
+		event.preventDefault();
+
+	}
+
+	this.dispose = function () {
+
+		this.domElement.removeEventListener( 'contextmenu', contextmenu, false );
+		this.domElement.removeEventListener( 'mousedown', _onMouseDown, false );
+		this.domElement.removeEventListener( 'mousemove', _onMouseMove, false );
+		this.domElement.removeEventListener( 'mouseup', _onMouseUp, false );
+
+		window.removeEventListener( 'keydown', _onKeyDown, false );
+		window.removeEventListener( 'keyup', _onKeyUp, false );
+
+	};
+
+	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, false );
+	this.domElement.addEventListener( 'mousemove', _onMouseMove, false );
+	this.domElement.addEventListener( 'mousedown', _onMouseDown, false );
+	this.domElement.addEventListener( 'mouseup', _onMouseUp, false );
+
+	window.addEventListener( 'keydown', _onKeyDown, false );
+	window.addEventListener( 'keyup', _onKeyUp, false );
+
+	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 - _Math.radToDeg( spherical.phi );
+		lon = _Math.radToDeg( spherical.theta );
+
+	}
+
+	this.handleResize();
+
+	setOrientation( this );
+
+};
+
+export { FirstPersonControls };

+ 19 - 0
examples/jsm/controls/FlyControls.d.ts

@@ -0,0 +1,19 @@
+import {
+  Camera
+} from '../../../src/Three';
+
+export class FlyControls {
+  constructor(object: Camera, domElement?: HTMLElement);
+
+  object: Camera;
+  domElement: HTMLElement | HTMLDocument;
+
+  movementSpeed: number;
+  rollSpeed: number;
+  dragToLook: boolean;
+  autoForward: boolean;
+
+  update(delta: number): void;
+  dispose(): void;
+
+}

+ 290 - 0
examples/jsm/controls/FlyControls.js

@@ -0,0 +1,290 @@
+/**
+ * @author James Baicoianu / http://www.baicoianu.com/
+ */
+
+import {
+	Quaternion,
+	Vector3
+} from "../../../build/three.module.js";
+
+var FlyControls = function ( object, domElement ) {
+
+	this.object = object;
+
+	this.domElement = ( domElement !== undefined ) ? domElement : document;
+	if ( domElement ) this.domElement.setAttribute( 'tabindex', - 1 );
+
+	// API
+
+	this.movementSpeed = 1.0;
+	this.rollSpeed = 0.005;
+
+	this.dragToLook = false;
+	this.autoForward = false;
+
+	// disable default target object behavior
+
+	// internals
+
+	this.tmpQuaternion = new 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 Vector3( 0, 0, 0 );
+	this.rotationVector = new Vector3( 0, 0, 0 );
+
+	this.keydown = function ( event ) {
+
+		if ( event.altKey ) {
+
+			return;
+
+		}
+
+		//event.preventDefault();
+
+		switch ( event.keyCode ) {
+
+			case 16: /* shift */ this.movementSpeedMultiplier = .1; break;
+
+			case 87: /*W*/ this.moveState.forward = 1; break;
+			case 83: /*S*/ this.moveState.back = 1; break;
+
+			case 65: /*A*/ this.moveState.left = 1; break;
+			case 68: /*D*/ this.moveState.right = 1; break;
+
+			case 82: /*R*/ this.moveState.up = 1; break;
+			case 70: /*F*/ this.moveState.down = 1; break;
+
+			case 38: /*up*/ this.moveState.pitchUp = 1; break;
+			case 40: /*down*/ this.moveState.pitchDown = 1; break;
+
+			case 37: /*left*/ this.moveState.yawLeft = 1; break;
+			case 39: /*right*/ this.moveState.yawRight = 1; break;
+
+			case 81: /*Q*/ this.moveState.rollLeft = 1; break;
+			case 69: /*E*/ this.moveState.rollRight = 1; break;
+
+		}
+
+		this.updateMovementVector();
+		this.updateRotationVector();
+
+	};
+
+	this.keyup = function ( event ) {
+
+		switch ( event.keyCode ) {
+
+			case 16: /* shift */ this.movementSpeedMultiplier = 1; break;
+
+			case 87: /*W*/ this.moveState.forward = 0; break;
+			case 83: /*S*/ this.moveState.back = 0; break;
+
+			case 65: /*A*/ this.moveState.left = 0; break;
+			case 68: /*D*/ this.moveState.right = 0; break;
+
+			case 82: /*R*/ this.moveState.up = 0; break;
+			case 70: /*F*/ this.moveState.down = 0; break;
+
+			case 38: /*up*/ this.moveState.pitchUp = 0; break;
+			case 40: /*down*/ this.moveState.pitchDown = 0; break;
+
+			case 37: /*left*/ this.moveState.yawLeft = 0; break;
+			case 39: /*right*/ this.moveState.yawRight = 0; break;
+
+			case 81: /*Q*/ this.moveState.rollLeft = 0; break;
+			case 69: /*E*/ this.moveState.rollRight = 0; break;
+
+		}
+
+		this.updateMovementVector();
+		this.updateRotationVector();
+
+	};
+
+	this.mousedown = function ( event ) {
+
+		if ( this.domElement !== document ) {
+
+			this.domElement.focus();
+
+		}
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		if ( this.dragToLook ) {
+
+			this.mouseStatus ++;
+
+		} else {
+
+			switch ( event.button ) {
+
+				case 0: this.moveState.forward = 1; break;
+				case 2: this.moveState.back = 1; break;
+
+			}
+
+			this.updateMovementVector();
+
+		}
+
+	};
+
+	this.mousemove = function ( event ) {
+
+		if ( ! this.dragToLook || this.mouseStatus > 0 ) {
+
+			var container = this.getContainerDimensions();
+			var halfWidth = container.size[ 0 ] / 2;
+			var halfHeight = container.size[ 1 ] / 2;
+
+			this.moveState.yawLeft = - ( ( event.pageX - container.offset[ 0 ] ) - halfWidth ) / halfWidth;
+			this.moveState.pitchDown = ( ( event.pageY - container.offset[ 1 ] ) - halfHeight ) / halfHeight;
+
+			this.updateRotationVector();
+
+		}
+
+	};
+
+	this.mouseup = function ( event ) {
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		if ( this.dragToLook ) {
+
+			this.mouseStatus --;
+
+			this.moveState.yawLeft = this.moveState.pitchDown = 0;
+
+		} else {
+
+			switch ( event.button ) {
+
+				case 0: this.moveState.forward = 0; break;
+				case 2: this.moveState.back = 0; break;
+
+			}
+
+			this.updateMovementVector();
+
+		}
+
+		this.updateRotationVector();
+
+	};
+
+	this.update = function ( delta ) {
+
+		var moveMult = delta * this.movementSpeed;
+		var rotMult = delta * this.rollSpeed;
+
+		this.object.translateX( this.moveVector.x * moveMult );
+		this.object.translateY( this.moveVector.y * moveMult );
+		this.object.translateZ( this.moveVector.z * moveMult );
+
+		this.tmpQuaternion.set( this.rotationVector.x * rotMult, this.rotationVector.y * rotMult, this.rotationVector.z * rotMult, 1 ).normalize();
+		this.object.quaternion.multiply( this.tmpQuaternion );
+
+		// expose the rotation vector for convenience
+		this.object.rotation.setFromQuaternion( this.object.quaternion, this.object.rotation.order );
+
+
+	};
+
+	this.updateMovementVector = function () {
+
+		var forward = ( this.moveState.forward || ( this.autoForward && ! this.moveState.back ) ) ? 1 : 0;
+
+		this.moveVector.x = ( - this.moveState.left + this.moveState.right );
+		this.moveVector.y = ( - this.moveState.down + this.moveState.up );
+		this.moveVector.z = ( - forward + this.moveState.back );
+
+		//console.log( 'move:', [ this.moveVector.x, this.moveVector.y, this.moveVector.z ] );
+
+	};
+
+	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.getContainerDimensions = function () {
+
+		if ( this.domElement != document ) {
+
+			return {
+				size: [ this.domElement.offsetWidth, this.domElement.offsetHeight ],
+				offset: [ this.domElement.offsetLeft, this.domElement.offsetTop ]
+			};
+
+		} else {
+
+			return {
+				size: [ window.innerWidth, window.innerHeight ],
+				offset: [ 0, 0 ]
+			};
+
+		}
+
+	};
+
+	function bind( scope, fn ) {
+
+		return function () {
+
+			fn.apply( scope, arguments );
+
+		};
+
+	}
+
+	function contextmenu( event ) {
+
+		event.preventDefault();
+
+	}
+
+	this.dispose = function () {
+
+		this.domElement.removeEventListener( 'contextmenu', contextmenu, false );
+		this.domElement.removeEventListener( 'mousedown', _mousedown, false );
+		this.domElement.removeEventListener( 'mousemove', _mousemove, false );
+		this.domElement.removeEventListener( 'mouseup', _mouseup, false );
+
+		window.removeEventListener( 'keydown', _keydown, false );
+		window.removeEventListener( 'keyup', _keyup, false );
+
+	};
+
+	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, false );
+
+	this.domElement.addEventListener( 'mousemove', _mousemove, false );
+	this.domElement.addEventListener( 'mousedown', _mousedown, false );
+	this.domElement.addEventListener( 'mouseup', _mouseup, false );
+
+	window.addEventListener( 'keydown', _keydown, false );
+	window.addEventListener( 'keyup', _keyup, false );
+
+	this.updateMovementVector();
+	this.updateRotationVector();
+
+};
+
+export { FlyControls };

+ 30 - 0
examples/jsm/controls/OrthographicTrackballControls.d.ts

@@ -0,0 +1,30 @@
+import { Camera, EventDispatcher } from '../../../src/Three';
+
+export class OrthographicTrackballControls extends EventDispatcher {
+  constructor(object: Camera, domElement?: HTMLElement);
+
+  object: Camera;
+  domElement: HTMLElement;
+
+  enabled: boolean;
+  screen: {left: number; top: number; width: number; height: number};
+  radius: number;
+  rotateSpeed: number;
+  zoomSpeed: number;
+  noRotate: boolean;
+  noZoom: boolean;
+  noPan: boolean;
+  noRoll: boolean;
+  staticMoving: boolean;
+  dynamicDampingFactor: number;
+  keys: number[];
+
+  handleResize(): void;
+  rotateCamera(): void;
+  zoomCamera(): void;
+  panCamera(): void;
+  update(): void;
+  reset(): void;
+  dispose(): void;
+
+}

+ 635 - 0
examples/jsm/controls/OrthographicTrackballControls.js

@@ -0,0 +1,635 @@
+/**
+ * @author Eberhard Graether / http://egraether.com/
+ * @author Mark Lundin 	/ http://mark-lundin.com
+ * @author Patrick Fuller / http://patrick-fuller.com
+ * @author Max Smolens / https://github.com/msmolens
+ */
+
+import {
+	EventDispatcher,
+	Quaternion,
+	Vector2,
+	Vector3
+} from "../../../build/three.module.js";
+
+var OrthographicTrackballControls = function ( object, domElement ) {
+
+	var _this = this;
+	var STATE = { NONE: - 1, ROTATE: 0, ZOOM: 1, PAN: 2, TOUCH_ROTATE: 3, TOUCH_ZOOM_PAN: 4 };
+
+	this.object = object;
+	this.domElement = ( domElement !== undefined ) ? domElement : document;
+
+	// API
+
+	this.enabled = true;
+
+	this.screen = { left: 0, top: 0, width: 0, height: 0 };
+
+	this.radius = 0;
+
+	this.rotateSpeed = 1.0;
+	this.zoomSpeed = 1.2;
+
+	this.noRotate = false;
+	this.noZoom = false;
+	this.noPan = false;
+	this.noRoll = false;
+
+	this.staticMoving = false;
+	this.dynamicDampingFactor = 0.2;
+
+	this.keys = [ 65 /*A*/, 83 /*S*/, 68 /*D*/ ];
+
+	// internals
+
+	this.target = new Vector3();
+
+	var EPS = 0.000001;
+
+	var _changed = true;
+
+	var _state = STATE.NONE,
+		_prevState = STATE.NONE,
+
+		_eye = new Vector3(),
+
+		_rotateStart = new Vector3(),
+		_rotateEnd = new Vector3(),
+
+		_zoomStart = new Vector2(),
+		_zoomEnd = new Vector2(),
+
+		_touchZoomDistanceStart = 0,
+		_touchZoomDistanceEnd = 0,
+
+		_panStart = new Vector2(),
+		_panEnd = new Vector2();
+
+	// for reset
+
+	this.target0 = this.target.clone();
+	this.position0 = this.object.position.clone();
+	this.up0 = this.object.up.clone();
+
+	this.left0 = this.object.left;
+	this.right0 = this.object.right;
+	this.top0 = this.object.top;
+	this.bottom0 = this.object.bottom;
+
+	// events
+
+	var changeEvent = { type: 'change' };
+	var startEvent = { type: 'start' };
+	var endEvent = { type: 'end' };
+
+
+	// methods
+
+	this.handleResize = function () {
+
+		if ( this.domElement === document ) {
+
+			this.screen.left = 0;
+			this.screen.top = 0;
+			this.screen.width = window.innerWidth;
+			this.screen.height = window.innerHeight;
+
+		} else {
+
+			var box = this.domElement.getBoundingClientRect();
+			// adjustments come from similar code in the jquery offset() function
+			var d = this.domElement.ownerDocument.documentElement;
+			this.screen.left = box.left + window.pageXOffset - d.clientLeft;
+			this.screen.top = box.top + window.pageYOffset - d.clientTop;
+			this.screen.width = box.width;
+			this.screen.height = box.height;
+
+		}
+
+		this.radius = 0.5 * Math.min( this.screen.width, this.screen.height );
+
+		this.left0 = this.object.left;
+		this.right0 = this.object.right;
+		this.top0 = this.object.top;
+		this.bottom0 = this.object.bottom;
+
+	};
+
+	var getMouseOnScreen = ( function () {
+
+		var vector = new Vector2();
+
+		return function getMouseOnScreen( pageX, pageY ) {
+
+			vector.set(
+				( pageX - _this.screen.left ) / _this.screen.width,
+				( pageY - _this.screen.top ) / _this.screen.height
+			);
+
+			return vector;
+
+		};
+
+	}() );
+
+	var getMouseProjectionOnBall = ( function () {
+
+		var vector = new Vector3();
+		var objectUp = new Vector3();
+		var mouseOnBall = new Vector3();
+
+		return function getMouseProjectionOnBall( pageX, pageY ) {
+
+			mouseOnBall.set(
+				( pageX - _this.screen.width * 0.5 - _this.screen.left ) / _this.radius,
+				( _this.screen.height * 0.5 + _this.screen.top - pageY ) / _this.radius,
+				0.0
+			);
+
+			var length = mouseOnBall.length();
+
+			if ( _this.noRoll ) {
+
+				if ( length < Math.SQRT1_2 ) {
+
+					mouseOnBall.z = Math.sqrt( 1.0 - length * length );
+
+				} else {
+
+					mouseOnBall.z = .5 / length;
+
+				}
+
+			} else if ( length > 1.0 ) {
+
+				mouseOnBall.normalize();
+
+			} else {
+
+				mouseOnBall.z = Math.sqrt( 1.0 - length * length );
+
+			}
+
+			_eye.copy( _this.object.position ).sub( _this.target );
+
+			vector.copy( _this.object.up ).setLength( mouseOnBall.y );
+			vector.add( objectUp.copy( _this.object.up ).cross( _eye ).setLength( mouseOnBall.x ) );
+			vector.add( _eye.setLength( mouseOnBall.z ) );
+
+			return vector;
+
+		};
+
+	}() );
+
+	this.rotateCamera = ( function () {
+
+		var axis = new Vector3(),
+			quaternion = new Quaternion();
+
+
+		return function rotateCamera() {
+
+			var angle = Math.acos( _rotateStart.dot( _rotateEnd ) / _rotateStart.length() / _rotateEnd.length() );
+
+			if ( angle ) {
+
+				axis.crossVectors( _rotateStart, _rotateEnd ).normalize();
+
+				angle *= _this.rotateSpeed;
+
+				quaternion.setFromAxisAngle( axis, - angle );
+
+				_eye.applyQuaternion( quaternion );
+				_this.object.up.applyQuaternion( quaternion );
+
+				_rotateEnd.applyQuaternion( quaternion );
+
+				if ( _this.staticMoving ) {
+
+					_rotateStart.copy( _rotateEnd );
+
+				} else {
+
+					quaternion.setFromAxisAngle( axis, angle * ( _this.dynamicDampingFactor - 1.0 ) );
+					_rotateStart.applyQuaternion( quaternion );
+
+				}
+
+				_changed = true;
+
+			}
+
+		};
+
+	}() );
+
+	this.zoomCamera = function () {
+
+		if ( _state === STATE.TOUCH_ZOOM_PAN ) {
+
+			var factor = _touchZoomDistanceEnd / _touchZoomDistanceStart;
+			_touchZoomDistanceStart = _touchZoomDistanceEnd;
+
+			_this.object.zoom *= factor;
+
+			_changed = true;
+
+		} else {
+
+			var factor = 1.0 + ( _zoomEnd.y - _zoomStart.y ) * _this.zoomSpeed;
+
+			if ( Math.abs( factor - 1.0 ) > EPS && factor > 0.0 ) {
+
+				_this.object.zoom /= factor;
+
+				if ( _this.staticMoving ) {
+
+					_zoomStart.copy( _zoomEnd );
+
+				} else {
+
+					_zoomStart.y += ( _zoomEnd.y - _zoomStart.y ) * this.dynamicDampingFactor;
+
+				}
+
+				_changed = true;
+
+			}
+
+		}
+
+	};
+
+	this.panCamera = ( function () {
+
+		var mouseChange = new Vector2(),
+			objectUp = new Vector3(),
+			pan = new Vector3();
+
+		return function panCamera() {
+
+			mouseChange.copy( _panEnd ).sub( _panStart );
+
+			if ( mouseChange.lengthSq() ) {
+
+				// Scale movement to keep clicked/dragged position under cursor
+				var scale_x = ( _this.object.right - _this.object.left ) / _this.object.zoom;
+				var scale_y = ( _this.object.top - _this.object.bottom ) / _this.object.zoom;
+				mouseChange.x *= scale_x;
+				mouseChange.y *= scale_y;
+
+				pan.copy( _eye ).cross( _this.object.up ).setLength( mouseChange.x );
+				pan.add( objectUp.copy( _this.object.up ).setLength( mouseChange.y ) );
+
+				_this.object.position.add( pan );
+				_this.target.add( pan );
+
+				if ( _this.staticMoving ) {
+
+					_panStart.copy( _panEnd );
+
+				} else {
+
+					_panStart.add( mouseChange.subVectors( _panEnd, _panStart ).multiplyScalar( _this.dynamicDampingFactor ) );
+
+				}
+
+				_changed = true;
+
+			}
+
+		};
+
+	}() );
+
+	this.update = function () {
+
+		_eye.subVectors( _this.object.position, _this.target );
+
+		if ( ! _this.noRotate ) {
+
+			_this.rotateCamera();
+
+		}
+
+		if ( ! _this.noZoom ) {
+
+			_this.zoomCamera();
+
+			if ( _changed ) {
+
+				_this.object.updateProjectionMatrix();
+
+			}
+
+		}
+
+		if ( ! _this.noPan ) {
+
+			_this.panCamera();
+
+		}
+
+		_this.object.position.addVectors( _this.target, _eye );
+
+		_this.object.lookAt( _this.target );
+
+		if ( _changed ) {
+
+			_this.dispatchEvent( changeEvent );
+
+			_changed = false;
+
+		}
+
+	};
+
+	this.reset = function () {
+
+		_state = STATE.NONE;
+		_prevState = STATE.NONE;
+
+		_this.target.copy( _this.target0 );
+		_this.object.position.copy( _this.position0 );
+		_this.object.up.copy( _this.up0 );
+
+		_eye.subVectors( _this.object.position, _this.target );
+
+		_this.object.left = _this.left0;
+		_this.object.right = _this.right0;
+		_this.object.top = _this.top0;
+		_this.object.bottom = _this.bottom0;
+
+		_this.object.lookAt( _this.target );
+
+		_this.dispatchEvent( changeEvent );
+
+		_changed = false;
+
+	};
+
+	// listeners
+
+	function keydown( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		window.removeEventListener( 'keydown', keydown );
+
+		_prevState = _state;
+
+		if ( _state !== STATE.NONE ) {
+
+			return;
+
+		} else if ( event.keyCode === _this.keys[ STATE.ROTATE ] && ! _this.noRotate ) {
+
+			_state = STATE.ROTATE;
+
+		} else if ( event.keyCode === _this.keys[ STATE.ZOOM ] && ! _this.noZoom ) {
+
+			_state = STATE.ZOOM;
+
+		} else if ( event.keyCode === _this.keys[ STATE.PAN ] && ! _this.noPan ) {
+
+			_state = STATE.PAN;
+
+		}
+
+	}
+
+	function keyup( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		_state = _prevState;
+
+		window.addEventListener( 'keydown', keydown, false );
+
+	}
+
+	function mousedown( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		if ( _state === STATE.NONE ) {
+
+			_state = event.button;
+
+		}
+
+		if ( _state === STATE.ROTATE && ! _this.noRotate ) {
+
+			_rotateStart.copy( getMouseProjectionOnBall( event.pageX, event.pageY ) );
+			_rotateEnd.copy( _rotateStart );
+
+		} else if ( _state === STATE.ZOOM && ! _this.noZoom ) {
+
+			_zoomStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
+			_zoomEnd.copy( _zoomStart );
+
+		} else if ( _state === STATE.PAN && ! _this.noPan ) {
+
+			_panStart.copy( getMouseOnScreen( event.pageX, event.pageY ) );
+			_panEnd.copy( _panStart );
+
+		}
+
+		document.addEventListener( 'mousemove', mousemove, false );
+		document.addEventListener( 'mouseup', mouseup, false );
+
+		_this.dispatchEvent( startEvent );
+
+	}
+
+	function mousemove( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		if ( _state === STATE.ROTATE && ! _this.noRotate ) {
+
+			_rotateEnd.copy( getMouseProjectionOnBall( event.pageX, event.pageY ) );
+
+		} else if ( _state === STATE.ZOOM && ! _this.noZoom ) {
+
+			_zoomEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
+
+		} else if ( _state === STATE.PAN && ! _this.noPan ) {
+
+			_panEnd.copy( getMouseOnScreen( event.pageX, event.pageY ) );
+
+		}
+
+	}
+
+	function mouseup( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		_state = STATE.NONE;
+
+		document.removeEventListener( 'mousemove', mousemove );
+		document.removeEventListener( 'mouseup', mouseup );
+		_this.dispatchEvent( endEvent );
+
+	}
+
+	function mousewheel( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		_zoomStart.y += event.deltaY * 0.01;
+		_this.dispatchEvent( startEvent );
+		_this.dispatchEvent( endEvent );
+
+	}
+
+	function touchstart( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		switch ( event.touches.length ) {
+
+			case 1:
+				_state = STATE.TOUCH_ROTATE;
+				_rotateStart.copy( getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
+				_rotateEnd.copy( _rotateStart );
+				break;
+
+			case 2:
+				_state = STATE.TOUCH_ZOOM_PAN;
+				var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+				var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+				_touchZoomDistanceEnd = _touchZoomDistanceStart = Math.sqrt( dx * dx + dy * dy );
+
+				var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
+				var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
+				_panStart.copy( getMouseOnScreen( x, y ) );
+				_panEnd.copy( _panStart );
+				break;
+
+			default:
+				_state = STATE.NONE;
+
+		}
+		_this.dispatchEvent( startEvent );
+
+	}
+
+	function touchmove( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		event.preventDefault();
+		event.stopPropagation();
+
+		switch ( event.touches.length ) {
+
+			case 1:
+				_rotateEnd.copy( getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
+				break;
+
+			case 2:
+				var dx = event.touches[ 0 ].pageX - event.touches[ 1 ].pageX;
+				var dy = event.touches[ 0 ].pageY - event.touches[ 1 ].pageY;
+				_touchZoomDistanceEnd = Math.sqrt( dx * dx + dy * dy );
+
+				var x = ( event.touches[ 0 ].pageX + event.touches[ 1 ].pageX ) / 2;
+				var y = ( event.touches[ 0 ].pageY + event.touches[ 1 ].pageY ) / 2;
+				_panEnd.copy( getMouseOnScreen( x, y ) );
+				break;
+
+			default:
+				_state = STATE.NONE;
+
+		}
+
+	}
+
+	function touchend( event ) {
+
+		if ( _this.enabled === false ) return;
+
+		switch ( event.touches.length ) {
+
+			case 1:
+				_rotateEnd.copy( getMouseProjectionOnBall( event.touches[ 0 ].pageX, event.touches[ 0 ].pageY ) );
+				_rotateStart.copy( _rotateEnd );
+				break;
+
+			case 2:
+				_touchZoomDistanceStart = _touchZoomDistanceEnd = 0;
+
+				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 ) );
+				_panStart.copy( _panEnd );
+				break;
+
+		}
+
+		_state = STATE.NONE;
+		_this.dispatchEvent( endEvent );
+
+	}
+
+	function contextmenu( event ) {
+
+		event.preventDefault();
+
+	}
+
+	this.dispose = function () {
+
+		this.domElement.removeEventListener( 'contextmenu', contextmenu, false );
+		this.domElement.removeEventListener( 'mousedown', mousedown, false );
+		this.domElement.removeEventListener( 'wheel', mousewheel, false );
+
+		this.domElement.removeEventListener( 'touchstart', touchstart, false );
+		this.domElement.removeEventListener( 'touchend', touchend, false );
+		this.domElement.removeEventListener( 'touchmove', touchmove, false );
+
+		document.removeEventListener( 'mousemove', mousemove, false );
+		document.removeEventListener( 'mouseup', mouseup, false );
+
+		window.removeEventListener( 'keydown', keydown, false );
+		window.removeEventListener( 'keyup', keyup, false );
+
+	};
+
+	this.domElement.addEventListener( 'contextmenu', contextmenu, false );
+	this.domElement.addEventListener( 'mousedown', mousedown, false );
+	this.domElement.addEventListener( 'wheel', mousewheel, false );
+
+	this.domElement.addEventListener( 'touchstart', touchstart, false );
+	this.domElement.addEventListener( 'touchend', touchend, false );
+	this.domElement.addEventListener( 'touchmove', touchmove, false );
+
+	window.addEventListener( 'keydown', keydown, false );
+	window.addEventListener( 'keyup', keyup, false );
+
+	this.handleResize();
+
+	// force an update at start
+	this.update();
+
+};
+
+OrthographicTrackballControls.prototype = Object.create( EventDispatcher.prototype );
+OrthographicTrackballControls.prototype.constructor = OrthographicTrackballControls;
+
+export { OrthographicTrackballControls };

+ 25 - 0
examples/jsm/controls/PointerLockControls.d.ts

@@ -0,0 +1,25 @@
+import {
+  Camera,
+  EventDispatcher,
+  Vector3
+} from '../../../src/Three';
+
+export class PointerLockControls extends EventDispatcher {
+  constructor(camera: Camera, domElement?: HTMLElement);
+
+  domElement: HTMLElement;
+  object: Camera;
+
+  // API
+
+  isLocked: boolean;
+
+  connect(): void;
+  disconnect(): void;
+  dispose(): void;
+  getObject(): Camera;
+  getDirection(v: Vector3): Vector3;
+  lock(): void;
+  unlock(): void;
+
+}

+ 134 - 0
examples/jsm/controls/PointerLockControls.js

@@ -0,0 +1,134 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author Mugen87 / https://github.com/Mugen87
+ */
+
+import {
+	Euler,
+	EventDispatcher,
+	Vector3
+} from "../../../build/three.module.js";
+
+var PointerLockControls = function ( camera, domElement ) {
+
+	this.domElement = domElement || document.body;
+	this.isLocked = false;
+
+	//
+	// internals
+	//
+
+	var scope = this;
+
+	var changeEvent = { type: 'change' };
+	var lockEvent = { type: 'lock' };
+	var unlockEvent = { type: 'unlock' };
+
+	var euler = new Euler( 0, 0, 0, 'YXZ' );
+
+	var PI_2 = Math.PI / 2;
+
+	function onMouseMove( event ) {
+
+		if ( scope.isLocked === false ) return;
+
+		var movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
+		var movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;
+
+		euler.setFromQuaternion( camera.quaternion );
+
+		euler.y -= movementX * 0.002;
+		euler.x -= movementY * 0.002;
+
+		euler.x = Math.max( - PI_2, Math.min( PI_2, euler.x ) );
+
+		camera.quaternion.setFromEuler( euler );
+
+		scope.dispatchEvent( changeEvent );
+
+	}
+
+	function onPointerlockChange() {
+
+		if ( document.pointerLockElement === scope.domElement ) {
+
+			scope.dispatchEvent( lockEvent );
+
+			scope.isLocked = true;
+
+		} else {
+
+			scope.dispatchEvent( unlockEvent );
+
+			scope.isLocked = false;
+
+		}
+
+	}
+
+	function onPointerlockError() {
+
+		console.error( 'THREE.PointerLockControls: Unable to use Pointer Lock API' );
+
+	}
+
+	this.connect = function () {
+
+		document.addEventListener( 'mousemove', onMouseMove, false );
+		document.addEventListener( 'pointerlockchange', onPointerlockChange, false );
+		document.addEventListener( 'pointerlockerror', onPointerlockError, false );
+
+	};
+
+	this.disconnect = function () {
+
+		document.removeEventListener( 'mousemove', onMouseMove, false );
+		document.removeEventListener( 'pointerlockchange', onPointerlockChange, false );
+		document.removeEventListener( 'pointerlockerror', onPointerlockError, false );
+
+	};
+
+	this.dispose = function () {
+
+		this.disconnect();
+
+	};
+
+	this.getObject = function () { // retaining this method for backward compatibility
+
+		return camera;
+
+	};
+
+	this.getDirection = function () {
+
+		var direction = new Vector3( 0, 0, - 1 );
+
+		return function ( v ) {
+
+			return v.copy( direction ).applyQuaternion( camera.quaternion );
+
+		};
+
+	}();
+
+	this.lock = function () {
+
+		this.domElement.requestPointerLock();
+
+	};
+
+	this.unlock = function () {
+
+		document.exitPointerLock();
+
+	};
+
+	this.connect();
+
+};
+
+PointerLockControls.prototype = Object.create( EventDispatcher.prototype );
+PointerLockControls.prototype.constructor = PointerLockControls;
+
+export { PointerLockControls };

+ 1 - 1
examples/jsm/controls/TrackballControls.js

@@ -499,7 +499,7 @@ var TrackballControls = function ( object, domElement ) {
 	function touchstart( event ) {
 
 		if ( _this.enabled === false ) return;
-		
+
 		event.preventDefault();
 
 		switch ( event.touches.length ) {

+ 46 - 0
examples/jsm/controls/TransformControls.d.ts

@@ -0,0 +1,46 @@
+import {
+  Object3D,
+  Camera,
+  Vector3,
+  Euler
+} from '../../../src/Three';
+
+export class TransformControls extends Object3D {
+  constructor(object: Camera, domElement?: HTMLElement);
+
+  domElement: HTMLElement;
+
+  // API
+
+  camera: Camera;
+  object: Object3D;
+  enabled: boolean;
+  axis: string;
+  mode: string;
+  translationSnap: Vector3;
+  rotationSnap: Vector3;
+  space: string;
+  size: number;
+  dragging: boolean;
+  showX: boolean;
+  showY: boolean;
+  showZ: boolean;
+  isTransformControls: boolean;
+  visible: boolean;
+
+  attach(object: Object3D): void;
+  detach(): void;
+  pointerHover(pointer: Object): void;
+  pointerDown(pointer: Object): void;
+  pointerMove(pointer: Object): void;
+  pointerUp(pointer: Object): void;
+  getMode(): string;
+  setMode(mode: string): void;
+  setTranslationSnap(translationSnap: Vector3): void;
+  setRotationSnap(rotationSnap: Euler): void;
+  setSize(size: number): void;
+  setSpace(space: string): void;
+  dispose(): void;
+  update(): void;
+
+}

+ 1603 - 0
examples/jsm/controls/TransformControls.js

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

+ 6 - 1
examples/jsm/exporters/GLTFExporter.js

@@ -1764,7 +1764,12 @@ GLTFExporter.prototype = {
 
 			} else {
 
-				object.updateMatrix();
+				if ( object.matrixAutoUpdate ) {
+
+					object.updateMatrix();
+
+				}
+
 				if ( ! equalArray( object.matrix.elements, [ 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1 ] ) ) {
 
 					gltfNode.matrix = object.matrix.elements;

+ 24 - 0
examples/jsm/loaders/BVHLoader.d.ts

@@ -0,0 +1,24 @@
+import {
+  AnimationClip,
+  Skeleton,
+  LoadingManager
+} from '../../../src/Three';
+
+
+export interface BVH {
+  clip: AnimationClip,
+  skeleton: Skeleton;
+}
+
+export class BVHLoader {
+  constructor(manager?: LoadingManager);
+  manager: LoadingManager;
+  path: string;
+  animateBonePositions: boolean;
+  animateBoneRotations: boolean;
+
+  load(url: string, onLoad: (bvh: BVH) => void, onProgress?: (event: ProgressEvent) => void, onError?: (event: ErrorEvent) => void) : void;
+  setPath(path: string) : this;
+
+  parse(text: string) : BVH;
+}

+ 428 - 0
examples/jsm/loaders/BVHLoader.js

@@ -0,0 +1,428 @@
+/**
+ * @author herzig / http://github.com/herzig
+ * @author Mugen87 / https://github.com/Mugen87
+ *
+ * Description: reads BVH files and outputs a single Skeleton and an AnimationClip
+ *
+ * Currently only supports bvh files containing a single root.
+ *
+ */
+
+import {
+	AnimationClip,
+	Bone,
+	DefaultLoadingManager,
+	FileLoader,
+	Quaternion,
+	QuaternionKeyframeTrack,
+	Skeleton,
+	Vector3,
+	VectorKeyframeTrack
+} from "../../../build/three.module.js";
+
+var BVHLoader = function ( manager ) {
+
+	this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
+
+	this.animateBonePositions = true;
+	this.animateBoneRotations = true;
+
+};
+
+BVHLoader.prototype = {
+
+	constructor: BVHLoader,
+
+	load: function ( url, onLoad, onProgress, onError ) {
+
+		var scope = this;
+
+		var loader = new FileLoader( scope.manager );
+		loader.setPath( scope.path );
+		loader.load( url, function ( text ) {
+
+			onLoad( scope.parse( text ) );
+
+		}, onProgress, onError );
+
+	},
+
+	setPath: function ( value ) {
+
+		this.path = value;
+		return this;
+
+	},
+
+	parse: function ( text ) {
+
+		/*
+			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 ) {
+
+			// read model structure
+
+			if ( nextLine( lines ) !== 'HIERARCHY' ) {
+
+				console.error( 'THREE.BVHLoader: HIERARCHY expected.' );
+
+			}
+
+			var list = []; // collects flat array of all bones
+			var root = readNode( lines, nextLine( lines ), list );
+
+			// read motion data
+
+			if ( nextLine( lines ) !== 'MOTION' ) {
+
+				console.error( 'THREE.BVHLoader: MOTION expected.' );
+
+			}
+
+			// number of frames
+
+			var tokens = nextLine( lines ).split( /[\s]+/ );
+			var numFrames = parseInt( tokens[ 1 ] );
+
+			if ( isNaN( numFrames ) ) {
+
+				console.error( 'THREE.BVHLoader: Failed to read number of frames.' );
+
+			}
+
+			// frame time
+
+			tokens = nextLine( lines ).split( /[\s]+/ );
+			var frameTime = parseFloat( tokens[ 2 ] );
+
+			if ( isNaN( frameTime ) ) {
+
+				console.error( 'THREE.BVHLoader: Failed to read frame time.' );
+
+			}
+
+			// read frame data line by line
+
+			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.
+		*/
+		function readFrameData( data, frameTime, bone ) {
+
+			// end sites have no motion data
+
+			if ( bone.type === 'ENDSITE' ) return;
+
+			// add keyframe
+
+			var keyframe = {
+				time: frameTime,
+				position: new Vector3(),
+				rotation: new Quaternion()
+			};
+
+			bone.frames.push( keyframe );
+
+			var quat = new Quaternion();
+
+			var vx = new Vector3( 1, 0, 0 );
+			var vy = new Vector3( 0, 1, 0 );
+			var vz = new 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.' );
+
+				}
+
+			}
+
+			// parse child nodes
+
+			for ( var i = 0; i < bone.children.length; i ++ ) {
+
+				readFrameData( data, frameTime, bone.children[ i ] );
+
+			}
+
+		}
+
+		/*
+		 Recursively parses the HIERACHY section of the BVH file
+
+		 - lines: all lines of the file. lines are consumed as we go along.
+		 - firstline: line containing the node type and name e.g. 'JOINT hip'
+		 - list: collects a flat list of nodes
+
+		 returns: a BVH node including children
+		*/
+		function readNode( lines, firstline, list ) {
+
+			var node = { name: '', type: '', frames: [] };
+			list.push( node );
+
+			// parse node type and name
+
+			var tokens = firstline.split( /[\s]+/ );
+
+			if ( tokens[ 0 ].toUpperCase() === 'END' && tokens[ 1 ].toUpperCase() === 'SITE' ) {
+
+				node.type = 'ENDSITE';
+				node.name = 'ENDSITE'; // bvh end sites have no name
+
+			} else {
+
+				node.name = tokens[ 1 ];
+				node.type = tokens[ 0 ].toUpperCase();
+
+			}
+
+			if ( nextLine( lines ) !== '{' ) {
+
+				console.error( 'THREE.BVHLoader: Expected opening { after type & name' );
+
+			}
+
+			// parse OFFSET
+
+			tokens = nextLine( lines ).split( /[\s]+/ );
+
+			if ( tokens[ 0 ] !== 'OFFSET' ) {
+
+				console.error( 'THREE.BVHLoader: Expected OFFSET but got: ' + tokens[ 0 ] );
+
+			}
+
+			if ( tokens.length !== 4 ) {
+
+				console.error( 'THREE.BVHLoader: Invalid number of values for OFFSET.' );
+
+			}
+
+			var offset = new Vector3(
+				parseFloat( tokens[ 1 ] ),
+				parseFloat( tokens[ 2 ] ),
+				parseFloat( tokens[ 3 ] )
+			);
+
+			if ( isNaN( offset.x ) || isNaN( offset.y ) || isNaN( offset.z ) ) {
+
+				console.error( 'THREE.BVHLoader: Invalid values of OFFSET.' );
+
+			}
+
+			node.offset = offset;
+
+			// parse CHANNELS definitions
+
+			if ( node.type !== 'ENDSITE' ) {
+
+				tokens = nextLine( lines ).split( /[\s]+/ );
+
+				if ( tokens[ 0 ] !== 'CHANNELS' ) {
+
+					console.error( 'THREE.BVHLoader: Expected CHANNELS definition.' );
+
+				}
+
+				var numChannels = parseInt( tokens[ 1 ] );
+				node.channels = tokens.splice( 2, numChannels );
+				node.children = [];
+
+			}
+
+			// read children
+
+			while ( true ) {
+
+				var line = nextLine( lines );
+
+				if ( line === '}' ) {
+
+					return node;
+
+				} else {
+
+					node.children.push( readNode( lines, line, list ) );
+
+				}
+
+			}
+
+		}
+
+		/*
+			recursively converts the internal bvh node structure to a Bone hierarchy
+
+			source: the bvh root node
+			list: pass an empty array, collects a flat list of all converted THREE.Bones
+
+			returns the root Bone
+		*/
+		function toTHREEBone( source, list ) {
+
+			var bone = new Bone();
+			list.push( bone );
+
+			bone.position.add( source.offset );
+			bone.name = source.name;
+
+			if ( source.type !== 'ENDSITE' ) {
+
+				for ( var i = 0; i < source.children.length; i ++ ) {
+
+					bone.add( toTHREEBone( source.children[ i ], list ) );
+
+				}
+
+			}
+
+			return bone;
+
+		}
+
+		/*
+			builds a AnimationClip from the keyframe data saved in each bone.
+
+			bone: bvh root node
+
+			returns: a AnimationClip containing position and quaternion tracks
+		*/
+		function toTHREEAnimation( bones ) {
+
+			var tracks = [];
+
+			// 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;
+
+				// track data
+
+				var times = [];
+				var positions = [];
+				var rotations = [];
+
+				for ( var j = 0; j < bone.frames.length; j ++ ) {
+
+					var frame = bone.frames[ j ];
+
+					times.push( frame.time );
+
+					// the animation system animates the position property,
+					// so we have to add the joint offset to all values
+
+					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 );
+
+				}
+
+				if ( scope.animateBonePositions ) {
+
+					tracks.push( new VectorKeyframeTrack( '.bones[' + bone.name + '].position', times, positions ) );
+
+				}
+
+				if ( scope.animateBoneRotations ) {
+
+					tracks.push( new QuaternionKeyframeTrack( '.bones[' + bone.name + '].quaternion', times, rotations ) );
+
+				}
+
+			}
+
+			return new AnimationClip( 'animation', - 1, tracks );
+
+		}
+
+		/*
+			returns the next non-empty line in lines
+		*/
+		function nextLine( lines ) {
+
+			var line;
+			// skip empty lines
+			while ( ( line = lines.shift().trim() ).length === 0 ) { }
+			return line;
+
+		}
+
+		var scope = this;
+
+		var lines = text.split( /[\r\n]+/g );
+
+		var bones = readBvh( lines );
+
+		var threeBones = [];
+		toTHREEBone( bones[ 0 ], threeBones );
+
+		var threeClip = toTHREEAnimation( bones );
+
+		return {
+			skeleton: new Skeleton( threeBones ),
+			clip: threeClip
+		};
+
+	}
+
+};
+
+export { BVHLoader };

+ 1 - 1
examples/jsm/loaders/GLTFLoader.d.ts

@@ -23,5 +23,5 @@ export class GLTFLoader {
   setResourcePath(path: string) : GLTFLoader;
   setCrossOrigin(value: string): void;
   setDRACOLoader(dracoLoader: object): void;
-  parse(data: ArrayBuffer, path: string, onLoad: (gltf: GLTF) => void, onError?: (event: ErrorEvent) => void) : void;
+  parse(data: ArrayBuffer | string, path: string, onLoad: (gltf: GLTF) => void, onError?: (event: ErrorEvent) => void) : void;
 }

+ 4 - 2
examples/jsm/loaders/GLTFLoader.js

@@ -933,7 +933,8 @@ var GLTFLoader = ( function () {
 
 				for ( var i = 0, il = params.length; i < il; i ++ ) {
 
-					target[ params[ i ] ] = source[ params[ i ] ];
+					var value = source[ params[ i ] ];
+					target[ params[ i ] ] = value.isColor ? value.clone() : value;
 
 				}
 
@@ -1391,7 +1392,7 @@ var GLTFLoader = ( function () {
 
 			if ( typeof gltfDef.extras === 'object' ) {
 
-				object.userData = gltfDef.extras;
+				Object.assign( object.userData, gltfDef.extras );
 
 			} else {
 
@@ -3088,6 +3089,7 @@ var GLTFLoader = ( function () {
 
 			if ( nodeDef.name !== undefined ) {
 
+				node.userData.name = nodeDef.name;
 				node.name = PropertyBinding.sanitizeNodeName( nodeDef.name );
 
 			}

+ 5 - 7
examples/jsm/loaders/MTLLoader.js

@@ -5,17 +5,13 @@
  */
 
 import {
-	BackSide,
-	ClampToEdgeWrapping,
 	Color,
 	DefaultLoadingManager,
-	DoubleSide,
 	FileLoader,
 	FrontSide,
 	Loader,
 	LoaderUtils,
 	MeshPhongMaterial,
-	MirroredRepeatWrapping,
 	RepeatWrapping,
 	TextureLoader,
 	Vector2
@@ -31,6 +27,8 @@ MTLLoader.prototype = {
 
 	constructor: MTLLoader,
 
+	crossOrigin: 'anonymous',
+
 	/**
 	 * Loads and parses a MTL asset from a URL.
 	 *
@@ -166,7 +164,7 @@ MTLLoader.prototype = {
 
 			} else {
 
-				if ( key === 'ka' || key === 'kd' || key === 'ks' || key ==='ke' ) {
+				if ( key === 'ka' || key === 'kd' || key === 'ks' || key === 'ke' ) {
 
 					var ss = value.split( delimiter_pattern, 3 );
 					info[ key ] = [ parseFloat( ss[ 0 ] ), parseFloat( ss[ 1 ] ), parseFloat( ss[ 2 ] ) ];
@@ -196,9 +194,9 @@ MTLLoader.prototype = {
  * @param baseUrl - Url relative to which textures are loaded
  * @param options - Set of options on how to construct the materials
  *                  side: Which side to apply the material
- *                        FrontSide (default), BackSide, DoubleSide
+ *                        FrontSide (default), THREE.BackSide, THREE.DoubleSide
  *                  wrap: What type of wrapping to apply for textures
- *                        RepeatWrapping (default), ClampToEdgeWrapping, MirroredRepeatWrapping
+ *                        RepeatWrapping (default), THREE.ClampToEdgeWrapping, THREE.MirroredRepeatWrapping
  *                  normalizeRGB: RGBs need to be normalized to 0-1 from 0-255
  *                                Default: false, assumed to be already normalized
  *                  ignoreZeroRGBs: Ignore values of RGBs (Ka,Kd,Ks) that are all 0's

+ 17 - 0
examples/jsm/loaders/PCDLoader.d.ts

@@ -0,0 +1,17 @@
+import {
+  Points,
+  LoadingManager
+} from '../../../src/Three';
+
+
+export class PCDLoader {
+  constructor(manager?: LoadingManager);
+  manager: LoadingManager;
+  littleEndian: boolean;
+  path: string;
+
+  load(url: string, onLoad: (points: Points) => void, onProgress?: (event: ProgressEvent) => void, onError?: (event: ErrorEvent) => void) : void;
+  setPath(path: string) : this;
+
+  parse(data: ArrayBuffer | string, url: string) : Points;
+}

+ 321 - 0
examples/jsm/loaders/PCDLoader.js

@@ -0,0 +1,321 @@
+/**
+ * @author Filipe Caixeta / http://filipecaixeta.com.br
+ * @author Mugen87 / https://github.com/Mugen87
+ *
+ * Description: A THREE loader for PCD ascii and binary files.
+ *
+ * Limitations: Compressed binary files are not supported.
+ *
+ */
+
+import {
+	BufferGeometry,
+	DefaultLoadingManager,
+	FileLoader,
+	Float32BufferAttribute,
+	LoaderUtils,
+	Points,
+	PointsMaterial,
+	VertexColors
+} from "../../../build/three.module.js";
+
+var PCDLoader = function ( manager ) {
+
+	this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
+	this.littleEndian = true;
+
+};
+
+
+PCDLoader.prototype = {
+
+	constructor: PCDLoader,
+
+	load: function ( url, onLoad, onProgress, onError ) {
+
+		var scope = this;
+
+		var loader = new FileLoader( scope.manager );
+		loader.setPath( scope.path );
+		loader.setResponseType( 'arraybuffer' );
+		loader.load( url, function ( data ) {
+
+			try {
+
+				onLoad( scope.parse( data, url ) );
+
+			} catch ( e ) {
+
+				if ( onError ) {
+
+					onError( e );
+
+				} else {
+
+					throw e;
+
+				}
+
+			}
+
+		}, onProgress, onError );
+
+	},
+
+	setPath: function ( value ) {
+
+		this.path = value;
+		return this;
+
+	},
+
+	parse: function ( data, url ) {
+
+		function parseHeader( data ) {
+
+			var PCDheader = {};
+			var result1 = data.search( /[\r\n]DATA\s(\S*)\s/i );
+			var result2 = /[\r\n]DATA\s(\S*)\s/i.exec( data.substr( result1 - 1 ) );
+
+			PCDheader.data = result2[ 1 ];
+			PCDheader.headerLen = result2[ 0 ].length + result1;
+			PCDheader.str = data.substr( 0, PCDheader.headerLen );
+
+			// remove comments
+
+			PCDheader.str = PCDheader.str.replace( /\#.*/gi, '' );
+
+			// parse
+
+			PCDheader.version = /VERSION (.*)/i.exec( PCDheader.str );
+			PCDheader.fields = /FIELDS (.*)/i.exec( PCDheader.str );
+			PCDheader.size = /SIZE (.*)/i.exec( PCDheader.str );
+			PCDheader.type = /TYPE (.*)/i.exec( PCDheader.str );
+			PCDheader.count = /COUNT (.*)/i.exec( PCDheader.str );
+			PCDheader.width = /WIDTH (.*)/i.exec( PCDheader.str );
+			PCDheader.height = /HEIGHT (.*)/i.exec( PCDheader.str );
+			PCDheader.viewpoint = /VIEWPOINT (.*)/i.exec( PCDheader.str );
+			PCDheader.points = /POINTS (.*)/i.exec( PCDheader.str );
+
+			// evaluate
+
+			if ( PCDheader.version !== null )
+				PCDheader.version = parseFloat( PCDheader.version[ 1 ] );
+
+			if ( PCDheader.fields !== null )
+				PCDheader.fields = PCDheader.fields[ 1 ].split( ' ' );
+
+			if ( PCDheader.type !== null )
+				PCDheader.type = PCDheader.type[ 1 ].split( ' ' );
+
+			if ( PCDheader.width !== null )
+				PCDheader.width = parseInt( PCDheader.width[ 1 ] );
+
+			if ( PCDheader.height !== null )
+				PCDheader.height = parseInt( PCDheader.height[ 1 ] );
+
+			if ( PCDheader.viewpoint !== null )
+				PCDheader.viewpoint = PCDheader.viewpoint[ 1 ];
+
+			if ( PCDheader.points !== null )
+				PCDheader.points = parseInt( PCDheader.points[ 1 ], 10 );
+
+			if ( PCDheader.points === null )
+				PCDheader.points = PCDheader.width * PCDheader.height;
+
+			if ( PCDheader.size !== null ) {
+
+				PCDheader.size = PCDheader.size[ 1 ].split( ' ' ).map( function ( x ) {
+
+					return parseInt( x, 10 );
+
+				} );
+
+			}
+
+			if ( PCDheader.count !== null ) {
+
+				PCDheader.count = PCDheader.count[ 1 ].split( ' ' ).map( function ( x ) {
+
+					return parseInt( x, 10 );
+
+				} );
+
+			} else {
+
+				PCDheader.count = [];
+
+				for ( var i = 0, l = PCDheader.fields.length; i < l; i ++ ) {
+
+					PCDheader.count.push( 1 );
+
+				}
+
+			}
+
+			PCDheader.offset = {};
+
+			var sizeSum = 0;
+
+			for ( var i = 0, l = PCDheader.fields.length; i < l; i ++ ) {
+
+				if ( PCDheader.data === 'ascii' ) {
+
+					PCDheader.offset[ PCDheader.fields[ i ] ] = i;
+
+				} else {
+
+					PCDheader.offset[ PCDheader.fields[ i ] ] = sizeSum;
+					sizeSum += PCDheader.size[ i ];
+
+				}
+
+			}
+
+			// for binary only
+
+			PCDheader.rowSize = sizeSum;
+
+			return PCDheader;
+
+		}
+
+		var textData = LoaderUtils.decodeText( new Uint8Array( data ) );
+
+		// parse header (always ascii format)
+
+		var PCDheader = parseHeader( textData );
+
+		// parse data
+
+		var position = [];
+		var normal = [];
+		var color = [];
+
+		// ascii
+
+		if ( PCDheader.data === 'ascii' ) {
+
+			var offset = PCDheader.offset;
+			var pcdData = textData.substr( PCDheader.headerLen );
+			var lines = pcdData.split( '\n' );
+
+			for ( var i = 0, l = lines.length; i < l; i ++ ) {
+
+				if ( lines[ i ] === '' ) continue;
+
+				var line = lines[ i ].split( ' ' );
+
+				if ( offset.x !== undefined ) {
+
+					position.push( parseFloat( line[ offset.x ] ) );
+					position.push( parseFloat( line[ offset.y ] ) );
+					position.push( parseFloat( line[ offset.z ] ) );
+
+				}
+
+				if ( offset.rgb !== undefined ) {
+
+					var rgb = parseFloat( line[ offset.rgb ] );
+					var r = ( rgb >> 16 ) & 0x0000ff;
+					var g = ( rgb >> 8 ) & 0x0000ff;
+					var b = ( rgb >> 0 ) & 0x0000ff;
+					color.push( r / 255, g / 255, b / 255 );
+
+				}
+
+				if ( offset.normal_x !== undefined ) {
+
+					normal.push( parseFloat( line[ offset.normal_x ] ) );
+					normal.push( parseFloat( line[ offset.normal_y ] ) );
+					normal.push( parseFloat( line[ offset.normal_z ] ) );
+
+				}
+
+			}
+
+		}
+
+		// binary
+
+		if ( PCDheader.data === 'binary_compressed' ) {
+
+			console.error( 'THREE.PCDLoader: binary_compressed files are not supported' );
+			return;
+
+		}
+
+		if ( PCDheader.data === 'binary' ) {
+
+			var dataview = new DataView( data, PCDheader.headerLen );
+			var offset = PCDheader.offset;
+
+			for ( var i = 0, row = 0; i < PCDheader.points; i ++, row += PCDheader.rowSize ) {
+
+				if ( offset.x !== undefined ) {
+
+					position.push( dataview.getFloat32( row + offset.x, this.littleEndian ) );
+					position.push( dataview.getFloat32( row + offset.y, this.littleEndian ) );
+					position.push( dataview.getFloat32( row + offset.z, this.littleEndian ) );
+
+				}
+
+				if ( offset.rgb !== undefined ) {
+
+					color.push( dataview.getUint8( row + offset.rgb + 2 ) / 255.0 );
+					color.push( dataview.getUint8( row + offset.rgb + 1 ) / 255.0 );
+					color.push( dataview.getUint8( row + offset.rgb + 0 ) / 255.0 );
+
+				}
+
+				if ( offset.normal_x !== undefined ) {
+
+					normal.push( dataview.getFloat32( row + offset.normal_x, this.littleEndian ) );
+					normal.push( dataview.getFloat32( row + offset.normal_y, this.littleEndian ) );
+					normal.push( dataview.getFloat32( row + offset.normal_z, this.littleEndian ) );
+
+				}
+
+			}
+
+		}
+
+		// build geometry
+
+		var geometry = new BufferGeometry();
+
+		if ( position.length > 0 ) geometry.addAttribute( 'position', new Float32BufferAttribute( position, 3 ) );
+		if ( normal.length > 0 ) geometry.addAttribute( 'normal', new Float32BufferAttribute( normal, 3 ) );
+		if ( color.length > 0 ) geometry.addAttribute( 'color', new Float32BufferAttribute( color, 3 ) );
+
+		geometry.computeBoundingSphere();
+
+		// build material
+
+		var material = new PointsMaterial( { size: 0.005 } );
+
+		if ( color.length > 0 ) {
+
+			material.vertexColors = VertexColors;
+
+		} else {
+
+			material.color.setHex( Math.random() * 0xffffff );
+
+		}
+
+		// build mesh
+
+		var mesh = new Points( geometry, material );
+		var name = url.split( '' ).reverse().join( '' );
+		name = /([^\/]*)/.exec( name );
+		name = name[ 1 ].split( '' ).reverse().join( '' );
+		mesh.name = name;
+
+		return mesh;
+
+	}
+
+};
+
+export { PCDLoader };

+ 18 - 0
examples/jsm/loaders/PLYLoader.d.ts

@@ -0,0 +1,18 @@
+import {
+  BufferGeometry,
+  LoadingManager
+} from '../../../src/Three';
+
+
+export class PLYLoader {
+  constructor(manager?: LoadingManager);
+  manager: LoadingManager;
+  propertyNameMapping: object;
+  path: string;
+
+  load(url: string, onLoad: (geometry: BufferGeometry) => void, onProgress?: (event: ProgressEvent) => void, onError?: (event: ErrorEvent) => void) : void;
+  setPath(path: string) : this;
+  setPropertyNameMapping(mapping: object) : void;
+
+  parse(data: ArrayBuffer | string) : BufferGeometry;
+}

+ 515 - 0
examples/jsm/loaders/PLYLoader.js

@@ -0,0 +1,515 @@
+/**
+ * @author Wei Meng / http://about.me/menway
+ *
+ * Description: A THREE loader for PLY ASCII files (known as the Polygon
+ * File Format or the Stanford Triangle Format).
+ *
+ * Limitations: ASCII decoding assumes file is UTF-8.
+ *
+ * Usage:
+ *	var loader = new PLYLoader();
+ *	loader.load('./models/ply/ascii/dolphins.ply', function (geometry) {
+ *
+ *		scene.add( new THREE.Mesh( geometry ) );
+ *
+ *	} );
+ *
+ * If the PLY file uses non standard property names, they can be mapped while
+ * loading. For example, the following maps the properties
+ * “diffuse_(red|green|blue)” in the file to standard color names.
+ *
+ * loader.setPropertyNameMapping( {
+ *	diffuse_red: 'red',
+ *	diffuse_green: 'green',
+ *	diffuse_blue: 'blue'
+ * } );
+ *
+ */
+
+import {
+	BufferGeometry,
+	DefaultLoadingManager,
+	FileLoader,
+	Float32BufferAttribute,
+	LoaderUtils
+} from "../../../build/three.module.js";
+
+
+var PLYLoader = function ( manager ) {
+
+	this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
+
+	this.propertyNameMapping = {};
+
+};
+
+PLYLoader.prototype = {
+
+	constructor: PLYLoader,
+
+	load: function ( url, onLoad, onProgress, onError ) {
+
+		var scope = this;
+
+		var loader = new FileLoader( this.manager );
+		loader.setPath( this.path );
+		loader.setResponseType( 'arraybuffer' );
+		loader.load( url, function ( text ) {
+
+			onLoad( scope.parse( text ) );
+
+		}, onProgress, onError );
+
+	},
+
+	setPath: function ( value ) {
+
+		this.path = value;
+		return this;
+
+	},
+
+	setPropertyNameMapping: function ( mapping ) {
+
+		this.propertyNameMapping = mapping;
+
+	},
+
+	parse: function ( data ) {
+
+		function parseHeader( data ) {
+
+			var patternHeader = /ply([\s\S]*)end_header\r?\n/;
+			var headerText = '';
+			var headerLength = 0;
+			var result = patternHeader.exec( data );
+
+			if ( result !== null ) {
+
+				headerText = result[ 1 ];
+				headerLength = result[ 0 ].length;
+
+			}
+
+			var header = {
+				comments: [],
+				elements: [],
+				headerLength: headerLength
+			};
+
+			var lines = headerText.split( '\n' );
+			var currentElement;
+			var lineType, lineValues;
+
+			function make_ply_element_property( propertValues, propertyNameMapping ) {
+
+				var property = { type: propertValues[ 0 ] };
+
+				if ( property.type === 'list' ) {
+
+					property.name = propertValues[ 3 ];
+					property.countType = propertValues[ 1 ];
+					property.itemType = propertValues[ 2 ];
+
+				} else {
+
+					property.name = propertValues[ 1 ];
+
+				}
+
+				if ( property.name in propertyNameMapping ) {
+
+					property.name = propertyNameMapping[ property.name ];
+
+				}
+
+				return property;
+
+			}
+
+			for ( var i = 0; i < lines.length; i ++ ) {
+
+				var line = lines[ i ];
+				line = line.trim();
+
+				if ( line === '' ) continue;
+
+				lineValues = line.split( /\s+/ );
+				lineType = lineValues.shift();
+				line = lineValues.join( ' ' );
+
+				switch ( lineType ) {
+
+					case 'format':
+
+						header.format = lineValues[ 0 ];
+						header.version = lineValues[ 1 ];
+
+						break;
+
+					case 'comment':
+
+						header.comments.push( line );
+
+						break;
+
+					case 'element':
+
+						if ( currentElement !== undefined ) {
+
+							header.elements.push( currentElement );
+
+						}
+
+						currentElement = {};
+						currentElement.name = lineValues[ 0 ];
+						currentElement.count = parseInt( lineValues[ 1 ] );
+						currentElement.properties = [];
+
+						break;
+
+					case 'property':
+
+						currentElement.properties.push( make_ply_element_property( lineValues, scope.propertyNameMapping ) );
+
+						break;
+
+
+					default:
+
+						console.log( 'unhandled', lineType, lineValues );
+
+				}
+
+			}
+
+			if ( currentElement !== undefined ) {
+
+				header.elements.push( currentElement );
+
+			}
+
+			return header;
+
+		}
+
+		function parseASCIINumber( n, type ) {
+
+			switch ( type ) {
+
+				case 'char': case 'uchar': case 'short': case 'ushort': case 'int': case 'uint':
+				case 'int8': case 'uint8': case 'int16': case 'uint16': case 'int32': case 'uint32':
+
+					return parseInt( n );
+
+				case 'float': case 'double': case 'float32': case 'float64':
+
+					return parseFloat( n );
+
+			}
+
+		}
+
+		function parseASCIIElement( properties, line ) {
+
+			var values = line.split( /\s+/ );
+
+			var element = {};
+
+			for ( var i = 0; i < properties.length; i ++ ) {
+
+				if ( properties[ i ].type === 'list' ) {
+
+					var list = [];
+					var n = parseASCIINumber( values.shift(), properties[ i ].countType );
+
+					for ( var j = 0; j < n; j ++ ) {
+
+						list.push( parseASCIINumber( values.shift(), properties[ i ].itemType ) );
+
+					}
+
+					element[ properties[ i ].name ] = list;
+
+				} else {
+
+					element[ properties[ i ].name ] = parseASCIINumber( values.shift(), properties[ i ].type );
+
+				}
+
+			}
+
+			return element;
+
+		}
+
+		function parseASCII( data, header ) {
+
+			// PLY ascii format specification, as per http://en.wikipedia.org/wiki/PLY_(file_format)
+
+			var buffer = {
+				indices: [],
+				vertices: [],
+				normals: [],
+				uvs: [],
+				faceVertexUvs: [],
+				colors: []
+			};
+
+			var result;
+
+			var patternBody = /end_header\s([\s\S]*)$/;
+			var body = '';
+			if ( ( result = patternBody.exec( data ) ) !== null ) {
+
+				body = result[ 1 ];
+
+			}
+
+			var lines = body.split( '\n' );
+			var currentElement = 0;
+			var currentElementCount = 0;
+
+			for ( var i = 0; i < lines.length; i ++ ) {
+
+				var line = lines[ i ];
+				line = line.trim();
+				if ( line === '' ) {
+
+					continue;
+
+				}
+
+				if ( currentElementCount >= header.elements[ currentElement ].count ) {
+
+					currentElement ++;
+					currentElementCount = 0;
+
+				}
+
+				var element = parseASCIIElement( header.elements[ currentElement ].properties, line );
+
+				handleElement( buffer, header.elements[ currentElement ].name, element );
+
+				currentElementCount ++;
+
+			}
+
+			return postProcess( buffer );
+
+		}
+
+		function postProcess( buffer ) {
+
+			var geometry = new BufferGeometry();
+
+			// mandatory buffer data
+
+			if ( buffer.indices.length > 0 ) {
+
+				geometry.setIndex( buffer.indices );
+
+			}
+
+			geometry.addAttribute( 'position', new Float32BufferAttribute( buffer.vertices, 3 ) );
+
+			// optional buffer data
+
+			if ( buffer.normals.length > 0 ) {
+
+				geometry.addAttribute( 'normal', new Float32BufferAttribute( buffer.normals, 3 ) );
+
+			}
+
+			if ( buffer.uvs.length > 0 ) {
+
+				geometry.addAttribute( 'uv', new Float32BufferAttribute( buffer.uvs, 2 ) );
+
+			}
+
+			if ( buffer.colors.length > 0 ) {
+
+				geometry.addAttribute( 'color', new Float32BufferAttribute( buffer.colors, 3 ) );
+
+			}
+
+			if ( buffer.faceVertexUvs.length > 0 ) {
+
+				geometry = geometry.toNonIndexed();
+				geometry.addAttribute( 'uv', new Float32BufferAttribute( buffer.faceVertexUvs, 2 ) );
+
+			}
+
+			geometry.computeBoundingSphere();
+
+			return geometry;
+
+		}
+
+		function handleElement( buffer, elementName, element ) {
+
+			if ( elementName === 'vertex' ) {
+
+				buffer.vertices.push( element.x, element.y, element.z );
+
+				if ( 'nx' in element && 'ny' in element && 'nz' in element ) {
+
+					buffer.normals.push( element.nx, element.ny, element.nz );
+
+				}
+
+				if ( 's' in element && 't' in element ) {
+
+					buffer.uvs.push( element.s, element.t );
+
+				}
+
+				if ( 'red' in element && 'green' in element && 'blue' in element ) {
+
+					buffer.colors.push( element.red / 255.0, element.green / 255.0, element.blue / 255.0 );
+
+				}
+
+			} else if ( elementName === 'face' ) {
+
+				var vertex_indices = element.vertex_indices || element.vertex_index; // issue #9338
+				var texcoord = element.texcoord;
+
+				if ( vertex_indices.length === 3 ) {
+
+					buffer.indices.push( vertex_indices[ 0 ], vertex_indices[ 1 ], vertex_indices[ 2 ] );
+
+					if ( texcoord && texcoord.length === 6 ) {
+
+						buffer.faceVertexUvs.push( texcoord[ 0 ], texcoord[ 1 ] );
+						buffer.faceVertexUvs.push( texcoord[ 2 ], texcoord[ 3 ] );
+						buffer.faceVertexUvs.push( texcoord[ 4 ], texcoord[ 5 ] );
+
+					}
+
+				} else if ( vertex_indices.length === 4 ) {
+
+					buffer.indices.push( vertex_indices[ 0 ], vertex_indices[ 1 ], vertex_indices[ 3 ] );
+					buffer.indices.push( vertex_indices[ 1 ], vertex_indices[ 2 ], vertex_indices[ 3 ] );
+
+				}
+
+			}
+
+		}
+
+		function binaryRead( dataview, at, type, little_endian ) {
+
+			switch ( type ) {
+
+				// corespondences for non-specific length types here match rply:
+				case 'int8':		case 'char':	 return [ dataview.getInt8( at ), 1 ];
+				case 'uint8':		case 'uchar':	 return [ dataview.getUint8( at ), 1 ];
+				case 'int16':		case 'short':	 return [ dataview.getInt16( at, little_endian ), 2 ];
+				case 'uint16':	case 'ushort': return [ dataview.getUint16( at, little_endian ), 2 ];
+				case 'int32':		case 'int':		 return [ dataview.getInt32( at, little_endian ), 4 ];
+				case 'uint32':	case 'uint':	 return [ dataview.getUint32( at, little_endian ), 4 ];
+				case 'float32': case 'float':	 return [ dataview.getFloat32( at, little_endian ), 4 ];
+				case 'float64': case 'double': return [ dataview.getFloat64( at, little_endian ), 8 ];
+
+			}
+
+		}
+
+		function binaryReadElement( dataview, at, properties, little_endian ) {
+
+			var element = {};
+			var result, read = 0;
+
+			for ( var i = 0; i < properties.length; i ++ ) {
+
+				if ( properties[ i ].type === 'list' ) {
+
+					var list = [];
+
+					result = binaryRead( dataview, at + read, properties[ i ].countType, little_endian );
+					var n = result[ 0 ];
+					read += result[ 1 ];
+
+					for ( var j = 0; j < n; j ++ ) {
+
+						result = binaryRead( dataview, at + read, properties[ i ].itemType, little_endian );
+						list.push( result[ 0 ] );
+						read += result[ 1 ];
+
+					}
+
+					element[ properties[ i ].name ] = list;
+
+				} else {
+
+					result = binaryRead( dataview, at + read, properties[ i ].type, little_endian );
+					element[ properties[ i ].name ] = result[ 0 ];
+					read += result[ 1 ];
+
+				}
+
+			}
+
+			return [ element, read ];
+
+		}
+
+		function parseBinary( data, header ) {
+
+			var buffer = {
+				indices: [],
+				vertices: [],
+				normals: [],
+				uvs: [],
+				faceVertexUvs: [],
+				colors: []
+			};
+
+			var little_endian = ( header.format === 'binary_little_endian' );
+			var body = new DataView( data, header.headerLength );
+			var result, loc = 0;
+
+			for ( var currentElement = 0; currentElement < header.elements.length; currentElement ++ ) {
+
+				for ( var currentElementCount = 0; currentElementCount < header.elements[ currentElement ].count; currentElementCount ++ ) {
+
+					result = binaryReadElement( body, loc, header.elements[ currentElement ].properties, little_endian );
+					loc += result[ 1 ];
+					var element = result[ 0 ];
+
+					handleElement( buffer, header.elements[ currentElement ].name, element );
+
+				}
+
+			}
+
+			return postProcess( buffer );
+
+		}
+
+		//
+
+		var geometry;
+		var scope = this;
+
+		if ( data instanceof ArrayBuffer ) {
+
+			var text = LoaderUtils.decodeText( new Uint8Array( data ) );
+			var header = parseHeader( text );
+
+			geometry = header.format === 'ascii' ? parseASCII( text, header ) : parseBinary( data, header );
+
+		} else {
+
+			geometry = parseASCII( data, parseHeader( data ) );
+
+		}
+
+		return geometry;
+
+	}
+
+};
+
+export { PLYLoader };

+ 16 - 0
examples/jsm/loaders/STLLoader.d.ts

@@ -0,0 +1,16 @@
+import {
+  BufferGeometry,
+  LoadingManager
+} from '../../../src/Three';
+
+
+export class STLLoader {
+  constructor(manager?: LoadingManager);
+  manager: LoadingManager;
+  path: string;
+
+  load(url: string, onLoad: (geometry: BufferGeometry) => void, onProgress?: (event: ProgressEvent) => void, onError?: (event: ErrorEvent) => void) : void;
+  setPath(path: string) : this;
+
+  parse(data: ArrayBuffer | string) : BufferGeometry;
+}

+ 4 - 7
examples/jsm/loaders/STLLoader.js

@@ -18,15 +18,15 @@
  * Usage:
  *  var loader = new STLLoader();
  *  loader.load( './models/stl/slotted_disk.stl', function ( geometry ) {
- *    scene.add( new Mesh( geometry ) );
+ *    scene.add( new THREE.Mesh( geometry ) );
  *  });
  *
  * For binary STLs geometry might contain colors for vertices. To use it:
  *  // use the same code to load STL as above
  *  if (geometry.hasColors) {
- *    material = new MeshPhongMaterial({ opacity: geometry.alpha, vertexColors: VertexColors });
+ *    material = new THREE.MeshPhongMaterial({ opacity: geometry.alpha, vertexColors: THREE.VertexColors });
  *  } else { .... }
- *  var mesh = new Mesh( geometry, material );
+ *  var mesh = new THREE.Mesh( geometry, material );
  */
 
 import {
@@ -36,10 +36,7 @@ import {
 	FileLoader,
 	Float32BufferAttribute,
 	LoaderUtils,
-	Mesh,
-	MeshPhongMaterial,
-	Vector3,
-	VertexColors
+	Vector3
 } from "../../../build/three.module.js";
 
 

+ 34 - 0
examples/jsm/loaders/SVGLoader.d.ts

@@ -0,0 +1,34 @@
+import {
+  LoadingManager,
+  ShapePath,
+  BufferGeometry,
+  Vector3
+} from '../../../src/Three';
+
+export interface SVGResult {
+  paths: ShapePath[];
+  xml: XMLDocument;
+}
+
+export interface StrokeStyle {
+  strokeColor: string;
+  strokeWidth: number;
+  strokeLineJoin: string;
+  strokeLineCap: string;
+  strokeMiterLimit: number;
+}
+
+export class SVGLoader {
+  constructor(manager?: LoadingManager);
+  manager: LoadingManager;
+  path: string;
+
+  load(url: string, onLoad: (data: SVGResult) => void, onProgress?: (event: ProgressEvent) => void, onError?: (event: ErrorEvent) => void) : void;
+  setPath(path: string) : this;
+
+  parse(text: string) : SVGResult;
+
+  static getStrokeStyle(width: number, color: string, opacity: number, lineJoin: string, lineCap: string, miterLimit: number): StrokeStyle;
+  static pointsToStroke(points: Vector3[], style: StrokeStyle, arcDivisions: number, minDistance: number ): BufferGeometry;
+  static pointsToStrokeWithBuffers(points: Vector3[], style: StrokeStyle, arcDivisions: number, minDistance: number, vertices: number[], normals: number[], uvs: number[], vertexOffset: number): number;
+}

+ 1996 - 0
examples/jsm/loaders/SVGLoader.js

@@ -0,0 +1,1996 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ * @author zz85 / http://joshuakoo.com/
+ * @author yomboprime / https://yombo.org
+ */
+
+import {
+	BufferGeometry,
+	Color,
+	DefaultLoadingManager,
+	FileLoader,
+	Float32BufferAttribute,
+	Matrix3,
+	Path,
+	ShapePath,
+	Vector2,
+	Vector3
+} from "../../../build/three.module.js";
+
+var SVGLoader = function ( manager ) {
+
+	this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
+
+};
+
+SVGLoader.prototype = {
+
+	constructor: SVGLoader,
+
+	load: function ( url, onLoad, onProgress, onError ) {
+
+		var scope = this;
+
+		var loader = new FileLoader( scope.manager );
+		loader.setPath( scope.path );
+		loader.load( url, function ( text ) {
+
+			onLoad( scope.parse( text ) );
+
+		}, onProgress, onError );
+
+	},
+
+	setPath: function ( value ) {
+
+		this.path = value;
+		return this;
+
+	},
+
+	parse: function ( text ) {
+
+		function parseNode( node, style ) {
+
+			if ( node.nodeType !== 1 ) return;
+
+			var transform = getNodeTransform( node );
+
+			var path = null;
+
+			switch ( node.nodeName ) {
+
+				case 'svg':
+					break;
+
+				case 'g':
+					style = parseStyle( node, style );
+					break;
+
+				case 'path':
+					style = parseStyle( node, style );
+					if ( node.hasAttribute( 'd' ) ) path = parsePathNode( node, style );
+					break;
+
+				case 'rect':
+					style = parseStyle( node, style );
+					path = parseRectNode( node, style );
+					break;
+
+				case 'polygon':
+					style = parseStyle( node, style );
+					path = parsePolygonNode( node, style );
+					break;
+
+				case 'polyline':
+					style = parseStyle( node, style );
+					path = parsePolylineNode( node, style );
+					break;
+
+				case 'circle':
+					style = parseStyle( node, style );
+					path = parseCircleNode( node, style );
+					break;
+
+				case 'ellipse':
+					style = parseStyle( node, style );
+					path = parseEllipseNode( node, style );
+					break;
+
+				case 'line':
+					style = parseStyle( node, style );
+					path = parseLineNode( node, style );
+					break;
+
+				default:
+					console.log( node );
+
+			}
+
+			if ( path ) {
+
+				if ( style.fill !== undefined && style.fill !== 'none' ) {
+
+					path.color.setStyle( style.fill );
+
+				}
+
+				transformPath( path, currentTransform );
+
+				paths.push( path );
+
+				path.userData = { node: node, style: style };
+
+			}
+
+			var nodes = node.childNodes;
+
+			for ( var i = 0; i < nodes.length; i ++ ) {
+
+				parseNode( nodes[ i ], style );
+
+			}
+
+			if ( transform ) {
+
+				transformStack.pop();
+
+				if ( transformStack.length > 0 ) {
+
+					currentTransform.copy( transformStack[ transformStack.length - 1 ] );
+
+				} else {
+
+					currentTransform.identity();
+
+				}
+
+			}
+
+		}
+
+		function parsePathNode( node ) {
+
+			var path = new ShapePath();
+
+			var point = new Vector2();
+			var control = new Vector2();
+
+			var firstPoint = new Vector2();
+			var isFirstPoint = true;
+			var doSetFirstPoint = false;
+
+			var d = node.getAttribute( 'd' );
+
+			// console.log( d );
+
+			var commands = d.match( /[a-df-z][^a-df-z]*/ig );
+
+			for ( var i = 0, l = commands.length; i < l; i ++ ) {
+
+				var command = commands[ i ];
+
+				var type = command.charAt( 0 );
+				var data = command.substr( 1 ).trim();
+
+				if ( isFirstPoint === true ) {
+
+					doSetFirstPoint = true;
+					isFirstPoint = false;
+
+				}
+
+				switch ( type ) {
+
+					case 'M':
+						var numbers = parseFloats( data );
+						for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) {
+
+							point.x = numbers[ j + 0 ];
+							point.y = numbers[ j + 1 ];
+							control.x = point.x;
+							control.y = point.y;
+
+							if ( j === 0 ) {
+
+								path.moveTo( point.x, point.y );
+
+							} else {
+
+								path.lineTo( point.x, point.y );
+
+							}
+
+							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
+						}
+						break;
+
+					case 'H':
+						var numbers = parseFloats( data );
+
+						for ( var j = 0, jl = numbers.length; j < jl; j ++ ) {
+
+							point.x = numbers[ j ];
+							control.x = point.x;
+							control.y = point.y;
+							path.lineTo( point.x, point.y );
+
+							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
+						}
+						break;
+
+					case 'V':
+						var numbers = parseFloats( data );
+
+						for ( var j = 0, jl = numbers.length; j < jl; j ++ ) {
+
+							point.y = numbers[ j ];
+							control.x = point.x;
+							control.y = point.y;
+							path.lineTo( point.x, point.y );
+
+							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
+						}
+						break;
+
+					case 'L':
+						var numbers = parseFloats( data );
+
+						for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) {
+
+							point.x = numbers[ j + 0 ];
+							point.y = numbers[ j + 1 ];
+							control.x = point.x;
+							control.y = point.y;
+							path.lineTo( point.x, point.y );
+
+							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
+						}
+						break;
+
+					case 'C':
+						var numbers = parseFloats( data );
+
+						for ( var j = 0, jl = numbers.length; j < jl; j += 6 ) {
+
+							path.bezierCurveTo(
+								numbers[ j + 0 ],
+								numbers[ j + 1 ],
+								numbers[ j + 2 ],
+								numbers[ j + 3 ],
+								numbers[ j + 4 ],
+								numbers[ j + 5 ]
+							);
+							control.x = numbers[ j + 2 ];
+							control.y = numbers[ j + 3 ];
+							point.x = numbers[ j + 4 ];
+							point.y = numbers[ j + 5 ];
+
+							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
+						}
+						break;
+
+					case 'S':
+						var numbers = parseFloats( data );
+
+						for ( var j = 0, jl = numbers.length; j < jl; j += 4 ) {
+
+							path.bezierCurveTo(
+								getReflection( point.x, control.x ),
+								getReflection( point.y, control.y ),
+								numbers[ j + 0 ],
+								numbers[ j + 1 ],
+								numbers[ j + 2 ],
+								numbers[ j + 3 ]
+							);
+							control.x = numbers[ j + 0 ];
+							control.y = numbers[ j + 1 ];
+							point.x = numbers[ j + 2 ];
+							point.y = numbers[ j + 3 ];
+
+							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
+						}
+						break;
+
+					case 'Q':
+						var numbers = parseFloats( data );
+
+						for ( var j = 0, jl = numbers.length; j < jl; j += 4 ) {
+
+							path.quadraticCurveTo(
+								numbers[ j + 0 ],
+								numbers[ j + 1 ],
+								numbers[ j + 2 ],
+								numbers[ j + 3 ]
+							);
+							control.x = numbers[ j + 0 ];
+							control.y = numbers[ j + 1 ];
+							point.x = numbers[ j + 2 ];
+							point.y = numbers[ j + 3 ];
+
+							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
+						}
+						break;
+
+					case 'T':
+						var numbers = parseFloats( data );
+
+						for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) {
+
+							var rx = getReflection( point.x, control.x );
+							var ry = getReflection( point.y, control.y );
+							path.quadraticCurveTo(
+								rx,
+								ry,
+								numbers[ j + 0 ],
+								numbers[ j + 1 ]
+							);
+							control.x = rx;
+							control.y = ry;
+							point.x = numbers[ j + 0 ];
+							point.y = numbers[ j + 1 ];
+
+							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
+						}
+						break;
+
+					case 'A':
+						var numbers = parseFloats( data );
+
+						for ( var j = 0, jl = numbers.length; j < jl; j += 7 ) {
+
+							var start = point.clone();
+							point.x = numbers[ j + 5 ];
+							point.y = numbers[ j + 6 ];
+							control.x = point.x;
+							control.y = point.y;
+							parseArcCommand(
+								path, numbers[ j ], numbers[ j + 1 ], numbers[ j + 2 ], numbers[ j + 3 ], numbers[ j + 4 ], start, point
+							);
+
+							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
+						}
+						break;
+
+					case 'm':
+						var numbers = parseFloats( data );
+
+						for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) {
+
+							point.x += numbers[ j + 0 ];
+							point.y += numbers[ j + 1 ];
+							control.x = point.x;
+							control.y = point.y;
+
+							if ( j === 0 ) {
+
+								path.moveTo( point.x, point.y );
+
+							} else {
+
+								path.lineTo( point.x, point.y );
+
+							}
+
+							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
+						}
+						break;
+
+					case 'h':
+						var numbers = parseFloats( data );
+
+						for ( var j = 0, jl = numbers.length; j < jl; j ++ ) {
+
+							point.x += numbers[ j ];
+							control.x = point.x;
+							control.y = point.y;
+							path.lineTo( point.x, point.y );
+
+							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
+						}
+						break;
+
+					case 'v':
+						var numbers = parseFloats( data );
+
+						for ( var j = 0, jl = numbers.length; j < jl; j ++ ) {
+
+							point.y += numbers[ j ];
+							control.x = point.x;
+							control.y = point.y;
+							path.lineTo( point.x, point.y );
+
+							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
+						}
+						break;
+
+					case 'l':
+						var numbers = parseFloats( data );
+
+						for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) {
+
+							point.x += numbers[ j + 0 ];
+							point.y += numbers[ j + 1 ];
+							control.x = point.x;
+							control.y = point.y;
+							path.lineTo( point.x, point.y );
+
+							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
+						}
+						break;
+
+					case 'c':
+						var numbers = parseFloats( data );
+
+						for ( var j = 0, jl = numbers.length; j < jl; j += 6 ) {
+
+							path.bezierCurveTo(
+								point.x + numbers[ j + 0 ],
+								point.y + numbers[ j + 1 ],
+								point.x + numbers[ j + 2 ],
+								point.y + numbers[ j + 3 ],
+								point.x + numbers[ j + 4 ],
+								point.y + numbers[ j + 5 ]
+							);
+							control.x = point.x + numbers[ j + 2 ];
+							control.y = point.y + numbers[ j + 3 ];
+							point.x += numbers[ j + 4 ];
+							point.y += numbers[ j + 5 ];
+
+							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
+						}
+						break;
+
+					case 's':
+						var numbers = parseFloats( data );
+
+						for ( var j = 0, jl = numbers.length; j < jl; j += 4 ) {
+
+							path.bezierCurveTo(
+								getReflection( point.x, control.x ),
+								getReflection( point.y, control.y ),
+								point.x + numbers[ j + 0 ],
+								point.y + numbers[ j + 1 ],
+								point.x + numbers[ j + 2 ],
+								point.y + numbers[ j + 3 ]
+							);
+							control.x = point.x + numbers[ j + 0 ];
+							control.y = point.y + numbers[ j + 1 ];
+							point.x += numbers[ j + 2 ];
+							point.y += numbers[ j + 3 ];
+
+							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
+						}
+						break;
+
+					case 'q':
+						var numbers = parseFloats( data );
+
+						for ( var j = 0, jl = numbers.length; j < jl; j += 4 ) {
+
+							path.quadraticCurveTo(
+								point.x + numbers[ j + 0 ],
+								point.y + numbers[ j + 1 ],
+								point.x + numbers[ j + 2 ],
+								point.y + numbers[ j + 3 ]
+							);
+							control.x = point.x + numbers[ j + 0 ];
+							control.y = point.y + numbers[ j + 1 ];
+							point.x += numbers[ j + 2 ];
+							point.y += numbers[ j + 3 ];
+
+							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
+						}
+						break;
+
+					case 't':
+						var numbers = parseFloats( data );
+
+						for ( var j = 0, jl = numbers.length; j < jl; j += 2 ) {
+
+							var rx = getReflection( point.x, control.x );
+							var ry = getReflection( point.y, control.y );
+							path.quadraticCurveTo(
+								rx,
+								ry,
+								point.x + numbers[ j + 0 ],
+								point.y + numbers[ j + 1 ]
+							);
+							control.x = rx;
+							control.y = ry;
+							point.x = point.x + numbers[ j + 0 ];
+							point.y = point.y + numbers[ j + 1 ];
+
+							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
+						}
+						break;
+
+					case 'a':
+						var numbers = parseFloats( data );
+
+						for ( var j = 0, jl = numbers.length; j < jl; j += 7 ) {
+
+							var start = point.clone();
+							point.x += numbers[ j + 5 ];
+							point.y += numbers[ j + 6 ];
+							control.x = point.x;
+							control.y = point.y;
+							parseArcCommand(
+								path, numbers[ j ], numbers[ j + 1 ], numbers[ j + 2 ], numbers[ j + 3 ], numbers[ j + 4 ], start, point
+							);
+
+							if ( j === 0 && doSetFirstPoint === true ) firstPoint.copy( point );
+
+						}
+						break;
+
+					case 'Z':
+					case 'z':
+						path.currentPath.autoClose = true;
+
+						if ( path.currentPath.curves.length > 0 ) {
+
+							// Reset point to beginning of Path
+							point.copy( firstPoint );
+							path.currentPath.currentPoint.copy( point );
+							isFirstPoint = true;
+
+						}
+						break;
+
+					default:
+						console.warn( command );
+
+				}
+
+				// console.log( type, parseFloats( data ), parseFloats( data ).length  )
+
+				doSetFirstPoint = false;
+
+			}
+
+			return path;
+
+		}
+
+		/**
+		 * https://www.w3.org/TR/SVG/implnote.html#ArcImplementationNotes
+		 * https://mortoray.com/2017/02/16/rendering-an-svg-elliptical-arc-as-bezier-curves/ Appendix: Endpoint to center arc conversion
+		 * From
+		 * rx ry x-axis-rotation large-arc-flag sweep-flag x y
+		 * To
+		 * aX, aY, xRadius, yRadius, aStartAngle, aEndAngle, aClockwise, aRotation
+		 */
+
+		function parseArcCommand( path, rx, ry, x_axis_rotation, large_arc_flag, sweep_flag, start, end ) {
+
+			x_axis_rotation = x_axis_rotation * Math.PI / 180;
+
+			// Ensure radii are positive
+			rx = Math.abs( rx );
+			ry = Math.abs( ry );
+
+			// Compute (x1′, y1′)
+			var dx2 = ( start.x - end.x ) / 2.0;
+			var dy2 = ( start.y - end.y ) / 2.0;
+			var x1p = Math.cos( x_axis_rotation ) * dx2 + Math.sin( x_axis_rotation ) * dy2;
+			var y1p = - Math.sin( x_axis_rotation ) * dx2 + Math.cos( x_axis_rotation ) * dy2;
+
+			// Compute (cx′, cy′)
+			var rxs = rx * rx;
+			var rys = ry * ry;
+			var x1ps = x1p * x1p;
+			var y1ps = y1p * y1p;
+
+			// Ensure radii are large enough
+			var cr = x1ps / rxs + y1ps / rys;
+
+			if ( cr > 1 ) {
+
+				// scale up rx,ry equally so cr == 1
+				var s = Math.sqrt( cr );
+				rx = s * rx;
+				ry = s * ry;
+				rxs = rx * rx;
+				rys = ry * ry;
+
+			}
+
+			var dq = ( rxs * y1ps + rys * x1ps );
+			var pq = ( rxs * rys - dq ) / dq;
+			var q = Math.sqrt( Math.max( 0, pq ) );
+			if ( large_arc_flag === sweep_flag ) q = - q;
+			var cxp = q * rx * y1p / ry;
+			var cyp = - q * ry * x1p / rx;
+
+			// Step 3: Compute (cx, cy) from (cx′, cy′)
+			var cx = Math.cos( x_axis_rotation ) * cxp - Math.sin( x_axis_rotation ) * cyp + ( start.x + end.x ) / 2;
+			var cy = Math.sin( x_axis_rotation ) * cxp + Math.cos( x_axis_rotation ) * cyp + ( start.y + end.y ) / 2;
+
+			// Step 4: Compute θ1 and Δθ
+			var theta = svgAngle( 1, 0, ( x1p - cxp ) / rx, ( y1p - cyp ) / ry );
+			var delta = svgAngle( ( x1p - cxp ) / rx, ( y1p - cyp ) / ry, ( - x1p - cxp ) / rx, ( - y1p - cyp ) / ry ) % ( Math.PI * 2 );
+
+			path.currentPath.absellipse( cx, cy, rx, ry, theta, theta + delta, sweep_flag === 0, x_axis_rotation );
+
+		}
+
+		function svgAngle( ux, uy, vx, vy ) {
+
+			var dot = ux * vx + uy * vy;
+			var len = Math.sqrt( ux * ux + uy * uy ) * Math.sqrt( vx * vx + vy * vy );
+			var ang = Math.acos( Math.max( - 1, Math.min( 1, dot / len ) ) ); // floating point precision, slightly over values appear
+			if ( ( ux * vy - uy * vx ) < 0 ) ang = - ang;
+			return ang;
+
+		}
+
+		/*
+		* According to https://www.w3.org/TR/SVG/shapes.html#RectElementRXAttribute
+		* rounded corner should be rendered to elliptical arc, but bezier curve does the job well enough
+		*/
+		function parseRectNode( node ) {
+
+			var x = parseFloat( node.getAttribute( 'x' ) || 0 );
+			var y = parseFloat( node.getAttribute( 'y' ) || 0 );
+			var rx = parseFloat( node.getAttribute( 'rx' ) || 0 );
+			var ry = parseFloat( node.getAttribute( 'ry' ) || 0 );
+			var w = parseFloat( node.getAttribute( 'width' ) );
+			var h = parseFloat( node.getAttribute( 'height' ) );
+
+			var path = new ShapePath();
+			path.moveTo( x + 2 * rx, y );
+			path.lineTo( x + w - 2 * rx, y );
+			if ( rx !== 0 || ry !== 0 ) path.bezierCurveTo( x + w, y, x + w, y, x + w, y + 2 * ry );
+			path.lineTo( x + w, y + h - 2 * ry );
+			if ( rx !== 0 || ry !== 0 ) path.bezierCurveTo( x + w, y + h, x + w, y + h, x + w - 2 * rx, y + h );
+			path.lineTo( x + 2 * rx, y + h );
+
+			if ( rx !== 0 || ry !== 0 ) {
+
+				path.bezierCurveTo( x, y + h, x, y + h, x, y + h - 2 * ry );
+
+			}
+
+			path.lineTo( x, y + 2 * ry );
+
+			if ( rx !== 0 || ry !== 0 ) {
+
+				path.bezierCurveTo( x, y, x, y, x + 2 * rx, y );
+
+			}
+
+			return path;
+
+		}
+
+		function parsePolygonNode( node ) {
+
+			function iterator( match, a, b ) {
+
+				var x = parseFloat( a );
+				var y = parseFloat( b );
+
+				if ( index === 0 ) {
+
+					path.moveTo( x, y );
+
+				} else {
+
+					path.lineTo( x, y );
+
+				}
+
+				index ++;
+
+			}
+
+			var regex = /(-?[\d\.?]+)[,|\s](-?[\d\.?]+)/g;
+
+			var path = new ShapePath();
+
+			var index = 0;
+
+			node.getAttribute( 'points' ).replace( regex, iterator );
+
+			path.currentPath.autoClose = true;
+
+			return path;
+
+		}
+
+		function parsePolylineNode( node ) {
+
+			function iterator( match, a, b ) {
+
+				var x = parseFloat( a );
+				var y = parseFloat( b );
+
+				if ( index === 0 ) {
+
+					path.moveTo( x, y );
+
+				} else {
+
+					path.lineTo( x, y );
+
+				}
+
+				index ++;
+
+			}
+
+			var regex = /(-?[\d\.?]+)[,|\s](-?[\d\.?]+)/g;
+
+			var path = new ShapePath();
+
+			var index = 0;
+
+			node.getAttribute( 'points' ).replace( regex, iterator );
+
+			path.currentPath.autoClose = false;
+
+			return path;
+
+		}
+
+		function parseCircleNode( node ) {
+
+			var x = parseFloat( node.getAttribute( 'cx' ) );
+			var y = parseFloat( node.getAttribute( 'cy' ) );
+			var r = parseFloat( node.getAttribute( 'r' ) );
+
+			var subpath = new Path();
+			subpath.absarc( x, y, r, 0, Math.PI * 2 );
+
+			var path = new ShapePath();
+			path.subPaths.push( subpath );
+
+			return path;
+
+		}
+
+		function parseEllipseNode( node ) {
+
+			var x = parseFloat( node.getAttribute( 'cx' ) );
+			var y = parseFloat( node.getAttribute( 'cy' ) );
+			var rx = parseFloat( node.getAttribute( 'rx' ) );
+			var ry = parseFloat( node.getAttribute( 'ry' ) );
+
+			var subpath = new Path();
+			subpath.absellipse( x, y, rx, ry, 0, Math.PI * 2 );
+
+			var path = new ShapePath();
+			path.subPaths.push( subpath );
+
+			return path;
+
+		}
+
+		function parseLineNode( node ) {
+
+			var x1 = parseFloat( node.getAttribute( 'x1' ) );
+			var y1 = parseFloat( node.getAttribute( 'y1' ) );
+			var x2 = parseFloat( node.getAttribute( 'x2' ) );
+			var y2 = parseFloat( node.getAttribute( 'y2' ) );
+
+			var path = new ShapePath();
+			path.moveTo( x1, y1 );
+			path.lineTo( x2, y2 );
+			path.currentPath.autoClose = false;
+
+			return path;
+
+		}
+
+		//
+
+		function parseStyle( node, style ) {
+
+			style = Object.assign( {}, style ); // clone style
+
+			function addStyle( svgName, jsName, adjustFunction ) {
+
+				if ( adjustFunction === undefined ) adjustFunction = function copy( v ) {
+
+					return v;
+
+				};
+
+				if ( node.hasAttribute( svgName ) ) style[ jsName ] = adjustFunction( node.getAttribute( svgName ) );
+				if ( node.style[ svgName ] !== '' ) style[ jsName ] = adjustFunction( node.style[ svgName ] );
+
+			}
+
+			function clamp( v ) {
+
+				return Math.max( 0, Math.min( 1, v ) );
+
+			}
+
+			function positive( v ) {
+
+				return Math.max( 0, v );
+
+			}
+
+			addStyle( 'fill', 'fill' );
+			addStyle( 'fill-opacity', 'fillOpacity', clamp );
+			addStyle( 'stroke', 'stroke' );
+			addStyle( 'stroke-opacity', 'strokeOpacity', clamp );
+			addStyle( 'stroke-width', 'strokeWidth', positive );
+			addStyle( 'stroke-linejoin', 'strokeLineJoin' );
+			addStyle( 'stroke-linecap', 'strokeLineCap' );
+			addStyle( 'stroke-miterlimit', 'strokeMiterLimit', positive );
+
+			return style;
+
+		}
+
+		// http://www.w3.org/TR/SVG11/implnote.html#PathElementImplementationNotes
+
+		function getReflection( a, b ) {
+
+			return a - ( b - a );
+
+		}
+
+		function parseFloats( string ) {
+
+			var array = string.split( /[\s,]+|(?=\s?[+\-])/ );
+
+			for ( var i = 0; i < array.length; i ++ ) {
+
+				var number = array[ i ];
+
+				// Handle values like 48.6037.7.8
+				// TODO Find a regex for this
+
+				if ( number.indexOf( '.' ) !== number.lastIndexOf( '.' ) ) {
+
+					var split = number.split( '.' );
+
+					for ( var s = 2; s < split.length; s ++ ) {
+
+						array.splice( i + s - 1, 0, '0.' + split[ s ] );
+
+					}
+
+				}
+
+				array[ i ] = parseFloat( number );
+
+			}
+
+			return array;
+
+
+		}
+
+		function getNodeTransform( node ) {
+
+			if ( ! node.hasAttribute( 'transform' ) ) {
+
+				return null;
+
+			}
+
+			var transform = parseNodeTransform( node );
+
+			if ( transform ) {
+
+				if ( transformStack.length > 0 ) {
+
+					transform.premultiply( transformStack[ transformStack.length - 1 ] );
+
+				}
+
+				currentTransform.copy( transform );
+				transformStack.push( transform );
+
+			}
+
+			return transform;
+
+		}
+
+		function parseNodeTransform( node ) {
+
+			var transform = new Matrix3();
+			var currentTransform = tempTransform0;
+			var transformsTexts = node.getAttribute( 'transform' ).split( ')' );
+
+			for ( var tIndex = transformsTexts.length - 1; tIndex >= 0; tIndex -- ) {
+
+				var transformText = transformsTexts[ tIndex ].trim();
+
+				if ( transformText === '' ) continue;
+
+				var openParPos = transformText.indexOf( '(' );
+				var closeParPos = transformText.length;
+
+				if ( openParPos > 0 && openParPos < closeParPos ) {
+
+					var transformType = transformText.substr( 0, openParPos );
+
+					var array = parseFloats( transformText.substr( openParPos + 1, closeParPos - openParPos - 1 ) );
+
+					currentTransform.identity();
+
+					switch ( transformType ) {
+
+						case "translate":
+
+							if ( array.length >= 1 ) {
+
+								var tx = array[ 0 ];
+								var ty = tx;
+
+								if ( array.length >= 2 ) {
+
+									ty = array[ 1 ];
+
+								}
+
+								currentTransform.translate( tx, ty );
+
+							}
+
+							break;
+
+						case "rotate":
+
+							if ( array.length >= 1 ) {
+
+								var angle = 0;
+								var cx = 0;
+								var cy = 0;
+
+								// Angle
+								angle = - array[ 0 ] * Math.PI / 180;
+
+								if ( array.length >= 3 ) {
+
+									// Center x, y
+									cx = array[ 1 ];
+									cy = array[ 2 ];
+
+								}
+
+								// Rotate around center (cx, cy)
+								tempTransform1.identity().translate( - cx, - cy );
+								tempTransform2.identity().rotate( angle );
+								tempTransform3.multiplyMatrices( tempTransform2, tempTransform1 );
+								tempTransform1.identity().translate( cx, cy );
+								currentTransform.multiplyMatrices( tempTransform1, tempTransform3 );
+
+							}
+
+							break;
+
+						case "scale":
+
+							if ( array.length >= 1 ) {
+
+								var scaleX = array[ 0 ];
+								var scaleY = scaleX;
+
+								if ( array.length >= 2 ) {
+
+									scaleY = array[ 1 ];
+
+								}
+
+								currentTransform.scale( scaleX, scaleY );
+
+							}
+
+							break;
+
+						case "skewX":
+
+							if ( array.length === 1 ) {
+
+								currentTransform.set(
+									1, Math.tan( array[ 0 ] * Math.PI / 180 ), 0,
+									0, 1, 0,
+									0, 0, 1
+								);
+
+							}
+
+							break;
+
+						case "skewY":
+
+							if ( array.length === 1 ) {
+
+								currentTransform.set(
+									1, 0, 0,
+									Math.tan( array[ 0 ] * Math.PI / 180 ), 1, 0,
+									0, 0, 1
+								);
+
+							}
+
+							break;
+
+						case "matrix":
+
+							if ( array.length === 6 ) {
+
+								currentTransform.set(
+									array[ 0 ], array[ 2 ], array[ 4 ],
+									array[ 1 ], array[ 3 ], array[ 5 ],
+									0, 0, 1
+								);
+
+							}
+
+							break;
+
+					}
+
+				}
+
+				transform.premultiply( currentTransform );
+
+			}
+
+			return transform;
+
+		}
+
+		function transformPath( path, m ) {
+
+			function transfVec2( v2 ) {
+
+				tempV3.set( v2.x, v2.y, 1 ).applyMatrix3( m );
+
+				v2.set( tempV3.x, tempV3.y );
+
+			}
+
+			var isRotated = isTransformRotated( m );
+
+			var subPaths = path.subPaths;
+
+			for ( var i = 0, n = subPaths.length; i < n; i ++ ) {
+
+				var subPath = subPaths[ i ];
+				var curves = subPath.curves;
+
+				for ( var j = 0; j < curves.length; j ++ ) {
+
+					var curve = curves[ j ];
+
+					if ( curve.isLineCurve ) {
+
+						transfVec2( curve.v1 );
+						transfVec2( curve.v2 );
+
+					} else if ( curve.isCubicBezierCurve ) {
+
+						transfVec2( curve.v0 );
+						transfVec2( curve.v1 );
+						transfVec2( curve.v2 );
+						transfVec2( curve.v3 );
+
+					} else if ( curve.isQuadraticBezierCurve ) {
+
+						transfVec2( curve.v0 );
+						transfVec2( curve.v1 );
+						transfVec2( curve.v2 );
+
+					} else if ( curve.isEllipseCurve ) {
+
+						if ( isRotated ) {
+
+							console.warn( "SVGLoader: Elliptic arc or ellipse rotation or skewing is not implemented." );
+
+						}
+
+						tempV2.set( curve.aX, curve.aY );
+						transfVec2( tempV2 );
+						curve.aX = tempV2.x;
+						curve.aY = tempV2.y;
+
+						curve.xRadius *= getTransformScaleX( m );
+						curve.yRadius *= getTransformScaleY( m );
+
+					}
+
+				}
+
+			}
+
+		}
+
+		function isTransformRotated( m ) {
+
+			return m.elements[ 1 ] !== 0 || m.elements[ 3 ] !== 0;
+
+		}
+
+		function getTransformScaleX( m ) {
+
+			var te = m.elements;
+			return Math.sqrt( te[ 0 ] * te[ 0 ] + te[ 1 ] * te[ 1 ] );
+
+		}
+
+		function getTransformScaleY( m ) {
+
+			var te = m.elements;
+			return Math.sqrt( te[ 3 ] * te[ 3 ] + te[ 4 ] * te[ 4 ] );
+
+		}
+
+		//
+
+		console.log( 'THREE.SVGLoader' );
+
+		var paths = [];
+
+		var transformStack = [];
+
+		var tempTransform0 = new Matrix3();
+		var tempTransform1 = new Matrix3();
+		var tempTransform2 = new Matrix3();
+		var tempTransform3 = new Matrix3();
+		var tempV2 = new Vector2();
+		var tempV3 = new Vector3();
+
+		var currentTransform = new Matrix3();
+
+		console.time( 'THREE.SVGLoader: DOMParser' );
+
+		var xml = new DOMParser().parseFromString( text, 'image/svg+xml' ); // application/xml
+
+		console.timeEnd( 'THREE.SVGLoader: DOMParser' );
+
+		console.time( 'THREE.SVGLoader: Parse' );
+
+		parseNode( xml.documentElement, {
+			fill: '#000',
+			fillOpacity: 1,
+			strokeOpacity: 1,
+			strokeWidth: 1,
+			strokeLineJoin: 'miter',
+			strokeLineCap: 'butt',
+			strokeMiterLimit: 4
+		} );
+
+		var data = { paths: paths, xml: xml.documentElement };
+
+		// console.log( paths );
+
+
+		console.timeEnd( 'THREE.SVGLoader: Parse' );
+
+		return data;
+
+	}
+
+};
+
+SVGLoader.getStrokeStyle = function ( width, color, opacity, lineJoin, lineCap, miterLimit ) {
+
+	// Param width: Stroke width
+	// Param color: As returned by Color.getStyle()
+	// Param opacity: 0 (transparent) to 1 (opaque)
+	// Param lineJoin: One of "round", "bevel", "miter" or "miter-limit"
+	// Param lineCap: One of "round", "square" or "butt"
+	// Param miterLimit: Maximum join length, in multiples of the "width" parameter (join is truncated if it exceeds that distance)
+	// Returns style object
+
+	width = width !== undefined ? width : 1;
+	color = color !== undefined ? color : '#000';
+	opacity = opacity !== undefined ? opacity : 1;
+	lineJoin = lineJoin !== undefined ? lineJoin : 'miter';
+	lineCap = lineCap !== undefined ? lineCap : 'butt';
+	miterLimit = miterLimit !== undefined ? miterLimit : 4;
+
+	return {
+		strokeColor: color,
+		strokeWidth: width,
+		strokeLineJoin: lineJoin,
+		strokeLineCap: lineCap,
+		strokeMiterLimit: miterLimit
+	};
+
+};
+
+SVGLoader.pointsToStroke = function ( points, style, arcDivisions, minDistance ) {
+
+	// Generates a stroke with some witdh around the given path.
+	// The path can be open or closed (last point equals to first point)
+	// Param points: Array of Vector2D (the path). Minimum 2 points.
+	// Param style: Object with SVG properties as returned by SVGLoader.getStrokeStyle(), or SVGLoader.parse() in the path.userData.style object
+	// Params arcDivisions: Arc divisions for round joins and endcaps. (Optional)
+	// Param minDistance: Points closer to this distance will be merged. (Optional)
+	// Returns BufferGeometry with stroke triangles (In plane z = 0). UV coordinates are generated ('u' along path. 'v' across it, from left to right)
+
+	var vertices = [];
+	var normals = [];
+	var uvs = [];
+
+	if ( SVGLoader.pointsToStrokeWithBuffers( points, style, arcDivisions, minDistance, vertices, normals, uvs ) === 0 ) {
+
+		return null;
+
+	}
+
+	var geometry = new BufferGeometry();
+	geometry.addAttribute( 'position', new Float32BufferAttribute( vertices, 3 ) );
+	geometry.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
+	geometry.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
+
+	return geometry;
+
+};
+
+SVGLoader.pointsToStrokeWithBuffers = function () {
+
+	var tempV2_1 = new Vector2();
+	var tempV2_2 = new Vector2();
+	var tempV2_3 = new Vector2();
+	var tempV2_4 = new Vector2();
+	var tempV2_5 = new Vector2();
+	var tempV2_6 = new Vector2();
+	var tempV2_7 = new Vector2();
+	var lastPointL = new Vector2();
+	var lastPointR = new Vector2();
+	var point0L = new Vector2();
+	var point0R = new Vector2();
+	var currentPointL = new Vector2();
+	var currentPointR = new Vector2();
+	var nextPointL = new Vector2();
+	var nextPointR = new Vector2();
+	var innerPoint = new Vector2();
+	var outerPoint = new Vector2();
+
+	return function ( points, style, arcDivisions, minDistance, vertices, normals, uvs, vertexOffset ) {
+
+		// This function can be called to update existing arrays or buffers.
+		// Accepts same parameters as pointsToStroke, plus the buffers and optional offset.
+		// Param vertexOffset: Offset vertices to start writing in the buffers (3 elements/vertex for vertices and normals, and 2 elements/vertex for uvs)
+		// Returns number of written vertices / normals / uvs pairs
+		// if 'vertices' parameter is undefined no triangles will be generated, but the returned vertices count will still be valid (useful to preallocate the buffers)
+		// 'normals' and 'uvs' buffers are optional
+
+		arcDivisions = arcDivisions !== undefined ? arcDivisions : 12;
+		minDistance = minDistance !== undefined ? minDistance : 0.001;
+		vertexOffset = vertexOffset !== undefined ? vertexOffset : 0;
+
+		// First ensure there are no duplicated points
+		points = removeDuplicatedPoints( points );
+
+		var numPoints = points.length;
+
+		if ( numPoints < 2 ) return 0;
+
+		var isClosed = points[ 0 ].equals( points[ numPoints - 1 ] );
+
+		var currentPoint;
+		var previousPoint = points[ 0 ];
+		var nextPoint;
+
+		var strokeWidth2 = style.strokeWidth / 2;
+
+		var deltaU = 1 / ( numPoints - 1 );
+		var u0 = 0;
+
+		var innerSideModified;
+		var joinIsOnLeftSide;
+		var isMiter;
+		var initialJoinIsOnLeftSide = false;
+
+		var numVertices = 0;
+		var currentCoordinate = vertexOffset * 3;
+		var currentCoordinateUV = vertexOffset * 2;
+
+		// Get initial left and right stroke points
+		getNormal( points[ 0 ], points[ 1 ], tempV2_1 ).multiplyScalar( strokeWidth2 );
+		lastPointL.copy( points[ 0 ] ).sub( tempV2_1 );
+		lastPointR.copy( points[ 0 ] ).add( tempV2_1 );
+		point0L.copy( lastPointL );
+		point0R.copy( lastPointR );
+
+		for ( var iPoint = 1; iPoint < numPoints; iPoint ++ ) {
+
+			currentPoint = points[ iPoint ];
+
+			// Get next point
+			if ( iPoint === numPoints - 1 ) {
+
+				if ( isClosed ) {
+
+					// Skip duplicated initial point
+					nextPoint = points[ 1 ];
+
+				} else nextPoint = undefined;
+
+			} else {
+
+				nextPoint = points[ iPoint + 1 ];
+
+			}
+
+			// Normal of previous segment in tempV2_1
+			var normal1 = tempV2_1;
+			getNormal( previousPoint, currentPoint, normal1 );
+
+			tempV2_3.copy( normal1 ).multiplyScalar( strokeWidth2 );
+			currentPointL.copy( currentPoint ).sub( tempV2_3 );
+			currentPointR.copy( currentPoint ).add( tempV2_3 );
+
+			var u1 = u0 + deltaU;
+
+			innerSideModified = false;
+
+			if ( nextPoint !== undefined ) {
+
+				// Normal of next segment in tempV2_2
+				getNormal( currentPoint, nextPoint, tempV2_2 );
+
+				tempV2_3.copy( tempV2_2 ).multiplyScalar( strokeWidth2 );
+				nextPointL.copy( currentPoint ).sub( tempV2_3 );
+				nextPointR.copy( currentPoint ).add( tempV2_3 );
+
+				joinIsOnLeftSide = true;
+				tempV2_3.subVectors( nextPoint, previousPoint );
+				if ( normal1.dot( tempV2_3 ) < 0 ) {
+
+					joinIsOnLeftSide = false;
+
+				}
+				if ( iPoint === 1 ) initialJoinIsOnLeftSide = joinIsOnLeftSide;
+
+				tempV2_3.subVectors( nextPoint, currentPoint );
+				tempV2_3.normalize();
+				var dot = Math.abs( normal1.dot( tempV2_3 ) );
+
+				// If path is straight, don't create join
+				if ( dot !== 0 ) {
+
+					// Compute inner and outer segment intersections
+					var miterSide = strokeWidth2 / dot;
+					tempV2_3.multiplyScalar( - miterSide );
+					tempV2_4.subVectors( currentPoint, previousPoint );
+					tempV2_5.copy( tempV2_4 ).setLength( miterSide ).add( tempV2_3 );
+					innerPoint.copy( tempV2_5 ).negate();
+					var miterLength2 = tempV2_5.length();
+					var segmentLengthPrev = tempV2_4.length();
+					tempV2_4.divideScalar( segmentLengthPrev );
+					tempV2_6.subVectors( nextPoint, currentPoint );
+					var segmentLengthNext = tempV2_6.length();
+					tempV2_6.divideScalar( segmentLengthNext );
+					// Check that previous and next segments doesn't overlap with the innerPoint of intersection
+					if ( tempV2_4.dot( innerPoint ) < segmentLengthPrev && tempV2_6.dot( innerPoint ) < segmentLengthNext ) {
+
+						innerSideModified = true;
+
+					}
+					outerPoint.copy( tempV2_5 ).add( currentPoint );
+					innerPoint.add( currentPoint );
+
+					isMiter = false;
+
+					if ( innerSideModified ) {
+
+						if ( joinIsOnLeftSide ) {
+
+							nextPointR.copy( innerPoint );
+							currentPointR.copy( innerPoint );
+
+						} else {
+
+							nextPointL.copy( innerPoint );
+							currentPointL.copy( innerPoint );
+
+						}
+
+					} else {
+
+						// The segment triangles are generated here if there was overlapping
+
+						makeSegmentTriangles();
+
+					}
+
+					switch ( style.strokeLineJoin ) {
+
+						case 'bevel':
+
+							makeSegmentWithBevelJoin( joinIsOnLeftSide, innerSideModified, u1 );
+
+							break;
+
+						case 'round':
+
+							// Segment triangles
+
+							createSegmentTrianglesWithMiddleSection( joinIsOnLeftSide, innerSideModified );
+
+							// Join triangles
+
+							if ( joinIsOnLeftSide ) {
+
+								makeCircularSector( currentPoint, currentPointL, nextPointL, u1, 0 );
+
+							} else {
+
+								makeCircularSector( currentPoint, nextPointR, currentPointR, u1, 1 );
+
+							}
+
+							break;
+
+						case 'miter':
+						case 'miter-clip':
+						default:
+
+							var miterFraction = ( strokeWidth2 * style.strokeMiterLimit ) / miterLength2;
+
+							if ( miterFraction < 1 ) {
+
+								// The join miter length exceeds the miter limit
+
+								if ( style.strokeLineJoin !== 'miter-clip' ) {
+
+									makeSegmentWithBevelJoin( joinIsOnLeftSide, innerSideModified, u1 );
+									break;
+
+								} else {
+
+									// Segment triangles
+
+									createSegmentTrianglesWithMiddleSection( joinIsOnLeftSide, innerSideModified );
+
+									// Miter-clip join triangles
+
+									if ( joinIsOnLeftSide ) {
+
+										tempV2_6.subVectors( outerPoint, currentPointL ).multiplyScalar( miterFraction ).add( currentPointL );
+										tempV2_7.subVectors( outerPoint, nextPointL ).multiplyScalar( miterFraction ).add( nextPointL );
+
+										addVertex( currentPointL, u1, 0 );
+										addVertex( tempV2_6, u1, 0 );
+										addVertex( currentPoint, u1, 0.5 );
+
+										addVertex( currentPoint, u1, 0.5 );
+										addVertex( tempV2_6, u1, 0 );
+										addVertex( tempV2_7, u1, 0 );
+
+										addVertex( currentPoint, u1, 0.5 );
+										addVertex( tempV2_7, u1, 0 );
+										addVertex( nextPointL, u1, 0 );
+
+									} else {
+
+										tempV2_6.subVectors( outerPoint, currentPointR ).multiplyScalar( miterFraction ).add( currentPointR );
+										tempV2_7.subVectors( outerPoint, nextPointR ).multiplyScalar( miterFraction ).add( nextPointR );
+
+										addVertex( currentPointR, u1, 1 );
+										addVertex( tempV2_6, u1, 1 );
+										addVertex( currentPoint, u1, 0.5 );
+
+										addVertex( currentPoint, u1, 0.5 );
+										addVertex( tempV2_6, u1, 1 );
+										addVertex( tempV2_7, u1, 1 );
+
+										addVertex( currentPoint, u1, 0.5 );
+										addVertex( tempV2_7, u1, 1 );
+										addVertex( nextPointR, u1, 1 );
+
+									}
+
+								}
+
+							} else {
+
+								// Miter join segment triangles
+
+								if ( innerSideModified ) {
+
+									// Optimized segment + join triangles
+
+									if ( joinIsOnLeftSide ) {
+
+										addVertex( lastPointR, u0, 1 );
+										addVertex( lastPointL, u0, 0 );
+										addVertex( outerPoint, u1, 0 );
+
+										addVertex( lastPointR, u0, 1 );
+										addVertex( outerPoint, u1, 0 );
+										addVertex( innerPoint, u1, 1 );
+
+									} else {
+
+										addVertex( lastPointR, u0, 1 );
+										addVertex( lastPointL, u0, 0 );
+										addVertex( outerPoint, u1, 1 );
+
+										addVertex( lastPointL, u0, 0 );
+										addVertex( innerPoint, u1, 0 );
+										addVertex( outerPoint, u1, 1 );
+
+									}
+
+
+									if ( joinIsOnLeftSide ) {
+
+										nextPointL.copy( outerPoint );
+
+									} else {
+
+										nextPointR.copy( outerPoint );
+
+									}
+
+
+								} else {
+
+									// Add extra miter join triangles
+
+									if ( joinIsOnLeftSide ) {
+
+										addVertex( currentPointL, u1, 0 );
+										addVertex( outerPoint, u1, 0 );
+										addVertex( currentPoint, u1, 0.5 );
+
+										addVertex( currentPoint, u1, 0.5 );
+										addVertex( outerPoint, u1, 0 );
+										addVertex( nextPointL, u1, 0 );
+
+									} else {
+
+										addVertex( currentPointR, u1, 1 );
+										addVertex( outerPoint, u1, 1 );
+										addVertex( currentPoint, u1, 0.5 );
+
+										addVertex( currentPoint, u1, 0.5 );
+										addVertex( outerPoint, u1, 1 );
+										addVertex( nextPointR, u1, 1 );
+
+									}
+
+								}
+
+								isMiter = true;
+
+							}
+
+							break;
+
+					}
+
+				} else {
+
+					// The segment triangles are generated here when two consecutive points are collinear
+
+					makeSegmentTriangles();
+
+				}
+
+			} else {
+
+				// The segment triangles are generated here if it is the ending segment
+
+				makeSegmentTriangles();
+
+			}
+
+			if ( ! isClosed && iPoint === numPoints - 1 ) {
+
+				// Start line endcap
+				addCapGeometry( points[ 0 ], point0L, point0R, joinIsOnLeftSide, true, u0 );
+
+			}
+
+			// Increment loop variables
+
+			u0 = u1;
+
+			previousPoint = currentPoint;
+
+			lastPointL.copy( nextPointL );
+			lastPointR.copy( nextPointR );
+
+		}
+
+		if ( ! isClosed ) {
+
+			// Ending line endcap
+			addCapGeometry( currentPoint, currentPointL, currentPointR, joinIsOnLeftSide, false, u1 );
+
+		} else if ( innerSideModified && vertices ) {
+
+			// Modify path first segment vertices to adjust to the segments inner and outer intersections
+
+			var lastOuter = outerPoint;
+			var lastInner = innerPoint;
+
+			if ( initialJoinIsOnLeftSide !== joinIsOnLeftSide ) {
+
+				lastOuter = innerPoint;
+				lastInner = outerPoint;
+
+			}
+
+			if ( joinIsOnLeftSide ) {
+
+				lastInner.toArray( vertices, 0 * 3 );
+				lastInner.toArray( vertices, 3 * 3 );
+
+				if ( isMiter ) {
+
+					lastOuter.toArray( vertices, 1 * 3 );
+
+				}
+
+			} else {
+
+				lastInner.toArray( vertices, 1 * 3 );
+				lastInner.toArray( vertices, 3 * 3 );
+
+				if ( isMiter ) {
+
+					lastOuter.toArray( vertices, 0 * 3 );
+
+				}
+
+			}
+
+		}
+
+		return numVertices;
+
+		// -- End of algorithm
+
+		// -- Functions
+
+		function getNormal( p1, p2, result ) {
+
+			result.subVectors( p2, p1 );
+			return result.set( - result.y, result.x ).normalize();
+
+		}
+
+		function addVertex( position, u, v ) {
+
+			if ( vertices ) {
+
+				vertices[ currentCoordinate ] = position.x;
+				vertices[ currentCoordinate + 1 ] = position.y;
+				vertices[ currentCoordinate + 2 ] = 0;
+
+				if ( normals ) {
+
+					normals[ currentCoordinate ] = 0;
+					normals[ currentCoordinate + 1 ] = 0;
+					normals[ currentCoordinate + 2 ] = 1;
+
+				}
+
+				currentCoordinate += 3;
+
+				if ( uvs ) {
+
+					uvs[ currentCoordinateUV ] = u;
+					uvs[ currentCoordinateUV + 1 ] = v;
+
+					currentCoordinateUV += 2;
+
+				}
+
+			}
+
+			numVertices += 3;
+
+		}
+
+		function makeCircularSector( center, p1, p2, u, v ) {
+
+			// param p1, p2: Points in the circle arc.
+			// p1 and p2 are in clockwise direction.
+
+			tempV2_1.copy( p1 ).sub( center ).normalize();
+			tempV2_2.copy( p2 ).sub( center ).normalize();
+
+			var angle = Math.PI;
+			var dot = tempV2_1.dot( tempV2_2 );
+			if ( Math.abs( dot ) < 1 ) angle = Math.abs( Math.acos( dot ) );
+
+			angle /= arcDivisions;
+
+			tempV2_3.copy( p1 );
+
+			for ( var i = 0, il = arcDivisions - 1; i < il; i ++ ) {
+
+				tempV2_4.copy( tempV2_3 ).rotateAround( center, angle );
+
+				addVertex( tempV2_3, u, v );
+				addVertex( tempV2_4, u, v );
+				addVertex( center, u, 0.5 );
+
+				tempV2_3.copy( tempV2_4 );
+
+			}
+
+			addVertex( tempV2_4, u, v );
+			addVertex( p2, u, v );
+			addVertex( center, u, 0.5 );
+
+		}
+
+		function makeSegmentTriangles() {
+
+			addVertex( lastPointR, u0, 1 );
+			addVertex( lastPointL, u0, 0 );
+			addVertex( currentPointL, u1, 0 );
+
+			addVertex( lastPointR, u0, 1 );
+			addVertex( currentPointL, u1, 1 );
+			addVertex( currentPointR, u1, 0 );
+
+		}
+
+		function makeSegmentWithBevelJoin( joinIsOnLeftSide, innerSideModified, u ) {
+
+			if ( innerSideModified ) {
+
+				// Optimized segment + bevel triangles
+
+				if ( joinIsOnLeftSide ) {
+
+					// Path segments triangles
+
+					addVertex( lastPointR, u0, 1 );
+					addVertex( lastPointL, u0, 0 );
+					addVertex( currentPointL, u1, 0 );
+
+					addVertex( lastPointR, u0, 1 );
+					addVertex( currentPointL, u1, 0 );
+					addVertex( innerPoint, u1, 1 );
+
+					// Bevel join triangle
+
+					addVertex( currentPointL, u, 0 );
+					addVertex( nextPointL, u, 0 );
+					addVertex( innerPoint, u, 0.5 );
+
+				} else {
+
+					// Path segments triangles
+
+					addVertex( lastPointR, u0, 1 );
+					addVertex( lastPointL, u0, 0 );
+					addVertex( currentPointR, u1, 1 );
+
+					addVertex( lastPointL, u0, 0 );
+					addVertex( innerPoint, u1, 0 );
+					addVertex( currentPointR, u1, 1 );
+
+					// Bevel join triangle
+
+					addVertex( currentPointR, u, 1 );
+					addVertex( nextPointR, u, 0 );
+					addVertex( innerPoint, u, 0.5 );
+
+				}
+
+			} else {
+
+				// Bevel join triangle. The segment triangles are done in the main loop
+
+				if ( joinIsOnLeftSide ) {
+
+					addVertex( currentPointL, u, 0 );
+					addVertex( nextPointL, u, 0 );
+					addVertex( currentPoint, u, 0.5 );
+
+				} else {
+
+					addVertex( currentPointR, u, 1 );
+					addVertex( nextPointR, u, 0 );
+					addVertex( currentPoint, u, 0.5 );
+
+				}
+
+			}
+
+		}
+
+		function createSegmentTrianglesWithMiddleSection( joinIsOnLeftSide, innerSideModified ) {
+
+			if ( innerSideModified ) {
+
+				if ( joinIsOnLeftSide ) {
+
+					addVertex( lastPointR, u0, 1 );
+					addVertex( lastPointL, u0, 0 );
+					addVertex( currentPointL, u1, 0 );
+
+					addVertex( lastPointR, u0, 1 );
+					addVertex( currentPointL, u1, 0 );
+					addVertex( innerPoint, u1, 1 );
+
+					addVertex( currentPointL, u0, 0 );
+					addVertex( currentPoint, u1, 0.5 );
+					addVertex( innerPoint, u1, 1 );
+
+					addVertex( currentPoint, u1, 0.5 );
+					addVertex( nextPointL, u0, 0 );
+					addVertex( innerPoint, u1, 1 );
+
+				} else {
+
+					addVertex( lastPointR, u0, 1 );
+					addVertex( lastPointL, u0, 0 );
+					addVertex( currentPointR, u1, 1 );
+
+					addVertex( lastPointL, u0, 0 );
+					addVertex( innerPoint, u1, 0 );
+					addVertex( currentPointR, u1, 1 );
+
+					addVertex( currentPointR, u0, 1 );
+					addVertex( innerPoint, u1, 0 );
+					addVertex( currentPoint, u1, 0.5 );
+
+					addVertex( currentPoint, u1, 0.5 );
+					addVertex( innerPoint, u1, 0 );
+					addVertex( nextPointR, u0, 1 );
+
+				}
+
+			}
+
+		}
+
+		function addCapGeometry( center, p1, p2, joinIsOnLeftSide, start, u ) {
+
+			// param center: End point of the path
+			// param p1, p2: Left and right cap points
+
+			switch ( style.strokeLineCap ) {
+
+				case 'round':
+
+					if ( start ) {
+
+						makeCircularSector( center, p2, p1, u, 0.5 );
+
+					} else {
+
+						makeCircularSector( center, p1, p2, u, 0.5 );
+
+					}
+
+					break;
+
+				case 'square':
+
+					if ( start ) {
+
+						tempV2_1.subVectors( p1, center );
+						tempV2_2.set( tempV2_1.y, - tempV2_1.x );
+
+						tempV2_3.addVectors( tempV2_1, tempV2_2 ).add( center );
+						tempV2_4.subVectors( tempV2_2, tempV2_1 ).add( center );
+
+						// Modify already existing vertices
+						if ( joinIsOnLeftSide ) {
+
+							tempV2_3.toArray( vertices, 1 * 3 );
+							tempV2_4.toArray( vertices, 0 * 3 );
+							tempV2_4.toArray( vertices, 3 * 3 );
+
+						} else {
+
+							tempV2_3.toArray( vertices, 1 * 3 );
+							tempV2_3.toArray( vertices, 3 * 3 );
+							tempV2_4.toArray( vertices, 0 * 3 );
+
+						}
+
+					} else {
+
+						tempV2_1.subVectors( p2, center );
+						tempV2_2.set( tempV2_1.y, - tempV2_1.x );
+
+						tempV2_3.addVectors( tempV2_1, tempV2_2 ).add( center );
+						tempV2_4.subVectors( tempV2_2, tempV2_1 ).add( center );
+
+						var vl = vertices.length;
+
+						// Modify already existing vertices
+						if ( joinIsOnLeftSide ) {
+
+							tempV2_3.toArray( vertices, vl - 1 * 3 );
+							tempV2_4.toArray( vertices, vl - 2 * 3 );
+							tempV2_4.toArray( vertices, vl - 4 * 3 );
+
+						} else {
+
+							tempV2_3.toArray( vertices, vl - 2 * 3 );
+							tempV2_4.toArray( vertices, vl - 1 * 3 );
+							tempV2_4.toArray( vertices, vl - 4 * 3 );
+
+						}
+
+					}
+
+					break;
+
+				case 'butt':
+				default:
+
+					// Nothing to do here
+					break;
+
+			}
+
+		}
+
+		function removeDuplicatedPoints( points ) {
+
+			// Creates a new array if necessary with duplicated points removed.
+			// This does not remove duplicated initial and ending points of a closed path.
+
+			var dupPoints = false;
+			for ( var i = 1, n = points.length - 1; i < n; i ++ ) {
+
+				if ( points[ i ].distanceTo( points[ i + 1 ] ) < minDistance ) {
+
+					dupPoints = true;
+					break;
+
+				}
+
+			}
+
+			if ( ! dupPoints ) return points;
+
+			var newPoints = [];
+			newPoints.push( points[ 0 ] );
+
+			for ( var i = 1, n = points.length - 1; i < n; i ++ ) {
+
+				if ( points[ i ].distanceTo( points[ i + 1 ] ) >= minDistance ) {
+
+					newPoints.push( points[ i ] );
+
+				}
+
+			}
+
+			newPoints.push( points[ points.length - 1 ] );
+
+			return newPoints;
+
+		}
+
+	};
+
+}();
+
+export { SVGLoader };

+ 16 - 0
examples/jsm/loaders/TGALoader.d.ts

@@ -0,0 +1,16 @@
+import {
+  Texture,
+  LoadingManager
+} from '../../../src/Three';
+
+
+export class TGALoader {
+  constructor(manager?: LoadingManager);
+  manager: LoadingManager;
+  path: string;
+
+  load(url: string, onLoad: (texture: Texture) => void, onProgress?: (event: ProgressEvent) => void, onError?: (event: ErrorEvent) => void) : void;
+  setPath(path: string) : this;
+
+  parse(data: ArrayBuffer) : Texture;
+}

+ 558 - 0
examples/jsm/loaders/TGALoader.js

@@ -0,0 +1,558 @@
+/*
+ * @author Daosheng Mu / https://github.com/DaoshengMu/
+ * @author mrdoob / http://mrdoob.com/
+ * @author takahirox / https://github.com/takahirox/
+ */
+
+import {
+	DefaultLoadingManager,
+	FileLoader,
+	Texture
+} from "../../../build/three.module.js";
+
+var TGALoader = function ( manager ) {
+
+	this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
+
+};
+
+TGALoader.prototype = {
+
+	constructor: TGALoader,
+
+	load: function ( url, onLoad, onProgress, onError ) {
+
+		var scope = this;
+
+		var texture = new Texture();
+
+		var loader = new FileLoader( this.manager );
+		loader.setResponseType( 'arraybuffer' );
+		loader.setPath( this.path );
+
+		loader.load( url, function ( buffer ) {
+
+			texture.image = scope.parse( buffer );
+			texture.needsUpdate = true;
+
+			if ( onLoad !== undefined ) {
+
+				onLoad( texture );
+
+			}
+
+		}, onProgress, onError );
+
+		return texture;
+
+	},
+
+	parse: function ( buffer ) {
+
+		// reference from vthibault, https://github.com/vthibault/roBrowser/blob/master/src/Loaders/Targa.js
+
+		function tgaCheckHeader( header ) {
+
+			switch ( header.image_type ) {
+
+				// check indexed type
+
+				case TGA_TYPE_INDEXED:
+				case TGA_TYPE_RLE_INDEXED:
+					if ( header.colormap_length > 256 || header.colormap_size !== 24 || header.colormap_type !== 1 ) {
+
+						console.error( 'THREE.TGALoader: Invalid type colormap data for indexed type.' );
+
+					}
+					break;
+
+				// check colormap type
+
+				case TGA_TYPE_RGB:
+				case TGA_TYPE_GREY:
+				case TGA_TYPE_RLE_RGB:
+				case TGA_TYPE_RLE_GREY:
+					if ( header.colormap_type ) {
+
+						console.error( 'THREE.TGALoader: Invalid type colormap data for colormap type.' );
+
+					}
+					break;
+
+				// What the need of a file without data ?
+
+				case TGA_TYPE_NO_DATA:
+					console.error( 'THREE.TGALoader: No data.' );
+
+				// Invalid type ?
+
+				default:
+					console.error( 'THREE.TGALoader: Invalid type "%s".', header.image_type );
+
+			}
+
+			// check image width and height
+
+			if ( header.width <= 0 || header.height <= 0 ) {
+
+				console.error( 'THREE.TGALoader: Invalid image size.' );
+
+			}
+
+			// check image pixel size
+
+			if ( header.pixel_size !== 8 && header.pixel_size !== 16 &&
+				header.pixel_size !== 24 && header.pixel_size !== 32 ) {
+
+				console.error( 'THREE.TGALoader: Invalid pixel size "%s".', header.pixel_size );
+
+			}
+
+		}
+
+		// parse tga image buffer
+
+		function tgaParse( use_rle, use_pal, header, offset, data ) {
+
+			var pixel_data,
+				pixel_size,
+				pixel_total,
+				palettes;
+
+			pixel_size = header.pixel_size >> 3;
+			pixel_total = header.width * header.height * pixel_size;
+
+			 // read palettes
+
+			 if ( use_pal ) {
+
+				 palettes = data.subarray( offset, offset += header.colormap_length * ( header.colormap_size >> 3 ) );
+
+			 }
+
+			 // read RLE
+
+			 if ( use_rle ) {
+
+				 pixel_data = new Uint8Array( pixel_total );
+
+				var c, count, i;
+				var shift = 0;
+				var pixels = new Uint8Array( pixel_size );
+
+				while ( shift < pixel_total ) {
+
+					c = data[ offset ++ ];
+					count = ( c & 0x7f ) + 1;
+
+					// RLE pixels
+
+					if ( c & 0x80 ) {
+
+						// bind pixel tmp array
+
+						for ( i = 0; i < pixel_size; ++ i ) {
+
+							pixels[ i ] = data[ offset ++ ];
+
+						}
+
+						// copy pixel array
+
+						for ( i = 0; i < count; ++ i ) {
+
+							pixel_data.set( pixels, shift + i * pixel_size );
+
+						}
+
+						shift += pixel_size * count;
+
+					} else {
+
+						// raw pixels
+
+						count *= pixel_size;
+						for ( i = 0; i < count; ++ i ) {
+
+							pixel_data[ shift + i ] = data[ offset ++ ];
+
+						}
+						shift += count;
+
+					}
+
+				}
+
+			 } else {
+
+				// raw pixels
+
+				pixel_data = data.subarray(
+					 offset, offset += ( use_pal ? header.width * header.height : pixel_total )
+				);
+
+			 }
+
+			 return {
+				pixel_data: pixel_data,
+				palettes: palettes
+			 };
+
+		}
+
+		function tgaGetImageData8bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image, palettes ) {
+
+			var colormap = palettes;
+			var color, i = 0, x, y;
+			var width = header.width;
+
+			for ( y = y_start; y !== y_end; y += y_step ) {
+
+				for ( x = x_start; x !== x_end; x += x_step, i ++ ) {
+
+					color = image[ i ];
+					imageData[ ( x + width * y ) * 4 + 3 ] = 255;
+					imageData[ ( x + width * y ) * 4 + 2 ] = colormap[ ( color * 3 ) + 0 ];
+					imageData[ ( x + width * y ) * 4 + 1 ] = colormap[ ( color * 3 ) + 1 ];
+					imageData[ ( x + width * y ) * 4 + 0 ] = colormap[ ( color * 3 ) + 2 ];
+
+				}
+
+			}
+
+			return imageData;
+
+		}
+
+		function tgaGetImageData16bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
+
+			var color, i = 0, x, y;
+			var width = header.width;
+
+			for ( y = y_start; y !== y_end; y += y_step ) {
+
+				for ( x = x_start; x !== x_end; x += x_step, i += 2 ) {
+
+					color = image[ i + 0 ] + ( image[ i + 1 ] << 8 ); // Inversed ?
+					imageData[ ( x + width * y ) * 4 + 0 ] = ( color & 0x7C00 ) >> 7;
+					imageData[ ( x + width * y ) * 4 + 1 ] = ( color & 0x03E0 ) >> 2;
+					imageData[ ( x + width * y ) * 4 + 2 ] = ( color & 0x001F ) >> 3;
+					imageData[ ( x + width * y ) * 4 + 3 ] = ( color & 0x8000 ) ? 0 : 255;
+
+				}
+
+			}
+
+			return imageData;
+
+		}
+
+		function tgaGetImageData24bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
+
+			var i = 0, x, y;
+			var width = header.width;
+
+			for ( y = y_start; y !== y_end; y += y_step ) {
+
+				for ( x = x_start; x !== x_end; x += x_step, i += 3 ) {
+
+					imageData[ ( x + width * y ) * 4 + 3 ] = 255;
+					imageData[ ( x + width * y ) * 4 + 2 ] = image[ i + 0 ];
+					imageData[ ( x + width * y ) * 4 + 1 ] = image[ i + 1 ];
+					imageData[ ( x + width * y ) * 4 + 0 ] = image[ i + 2 ];
+
+				}
+
+			}
+
+			return imageData;
+
+		}
+
+		function tgaGetImageData32bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
+
+			var i = 0, x, y;
+			var width = header.width;
+
+			for ( y = y_start; y !== y_end; y += y_step ) {
+
+				for ( x = x_start; x !== x_end; x += x_step, i += 4 ) {
+
+					imageData[ ( x + width * y ) * 4 + 2 ] = image[ i + 0 ];
+					imageData[ ( x + width * y ) * 4 + 1 ] = image[ i + 1 ];
+					imageData[ ( x + width * y ) * 4 + 0 ] = image[ i + 2 ];
+					imageData[ ( x + width * y ) * 4 + 3 ] = image[ i + 3 ];
+
+				}
+
+			}
+
+			return imageData;
+
+		}
+
+		function tgaGetImageDataGrey8bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
+
+			var color, i = 0, x, y;
+			var width = header.width;
+
+			for ( y = y_start; y !== y_end; y += y_step ) {
+
+				for ( x = x_start; x !== x_end; x += x_step, i ++ ) {
+
+					color = image[ i ];
+					imageData[ ( x + width * y ) * 4 + 0 ] = color;
+					imageData[ ( x + width * y ) * 4 + 1 ] = color;
+					imageData[ ( x + width * y ) * 4 + 2 ] = color;
+					imageData[ ( x + width * y ) * 4 + 3 ] = 255;
+
+				}
+
+			}
+
+			return imageData;
+
+		}
+
+		function tgaGetImageDataGrey16bits( imageData, y_start, y_step, y_end, x_start, x_step, x_end, image ) {
+
+			var i = 0, x, y;
+			var width = header.width;
+
+			for ( y = y_start; y !== y_end; y += y_step ) {
+
+				for ( x = x_start; x !== x_end; x += x_step, i += 2 ) {
+
+					imageData[ ( x + width * y ) * 4 + 0 ] = image[ i + 0 ];
+					imageData[ ( x + width * y ) * 4 + 1 ] = image[ i + 0 ];
+					imageData[ ( x + width * y ) * 4 + 2 ] = image[ i + 0 ];
+					imageData[ ( x + width * y ) * 4 + 3 ] = image[ i + 1 ];
+
+				}
+
+			}
+
+			return imageData;
+
+		}
+
+		function getTgaRGBA( data, width, height, image, palette ) {
+
+			var x_start,
+				y_start,
+				x_step,
+				y_step,
+				x_end,
+				y_end;
+
+			switch ( ( header.flags & TGA_ORIGIN_MASK ) >> TGA_ORIGIN_SHIFT ) {
+
+				default:
+				case TGA_ORIGIN_UL:
+					x_start = 0;
+					x_step = 1;
+					x_end = width;
+					y_start = 0;
+					y_step = 1;
+					y_end = height;
+					break;
+
+				case TGA_ORIGIN_BL:
+					x_start = 0;
+					x_step = 1;
+					x_end = width;
+					y_start = height - 1;
+					y_step = - 1;
+					y_end = - 1;
+					break;
+
+				case TGA_ORIGIN_UR:
+					x_start = width - 1;
+					x_step = - 1;
+					x_end = - 1;
+					y_start = 0;
+					y_step = 1;
+					y_end = height;
+					break;
+
+				case TGA_ORIGIN_BR:
+					x_start = width - 1;
+					x_step = - 1;
+					x_end = - 1;
+					y_start = height - 1;
+					y_step = - 1;
+					y_end = - 1;
+					break;
+
+			}
+
+			if ( use_grey ) {
+
+				switch ( header.pixel_size ) {
+
+					case 8:
+						tgaGetImageDataGrey8bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
+						break;
+
+					case 16:
+						tgaGetImageDataGrey16bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
+						break;
+
+					default:
+						console.error( 'THREE.TGALoader: Format not supported.' );
+						break;
+
+				}
+
+			} else {
+
+				switch ( header.pixel_size ) {
+
+					case 8:
+						tgaGetImageData8bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image, palette );
+						break;
+
+					case 16:
+						tgaGetImageData16bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
+						break;
+
+					case 24:
+						tgaGetImageData24bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
+						break;
+
+					case 32:
+						tgaGetImageData32bits( data, y_start, y_step, y_end, x_start, x_step, x_end, image );
+						break;
+
+					default:
+						console.error( 'THREE.TGALoader: Format not supported.' );
+						break;
+
+				}
+
+			}
+
+			// Load image data according to specific method
+			// var func = 'tgaGetImageData' + (use_grey ? 'Grey' : '') + (header.pixel_size) + 'bits';
+			// func(data, y_start, y_step, y_end, x_start, x_step, x_end, width, image, palette );
+			return data;
+
+		}
+
+		// TGA constants
+
+		var TGA_TYPE_NO_DATA = 0,
+			TGA_TYPE_INDEXED = 1,
+			TGA_TYPE_RGB = 2,
+			TGA_TYPE_GREY = 3,
+			TGA_TYPE_RLE_INDEXED = 9,
+			TGA_TYPE_RLE_RGB = 10,
+			TGA_TYPE_RLE_GREY = 11,
+
+			TGA_ORIGIN_MASK = 0x30,
+			TGA_ORIGIN_SHIFT = 0x04,
+			TGA_ORIGIN_BL = 0x00,
+			TGA_ORIGIN_BR = 0x01,
+			TGA_ORIGIN_UL = 0x02,
+			TGA_ORIGIN_UR = 0x03;
+
+		if ( buffer.length < 19 ) console.error( 'THREE.TGALoader: Not enough data to contain header.' );
+
+		var content = new Uint8Array( buffer ),
+			offset = 0,
+			header = {
+				id_length: content[ offset ++ ],
+				colormap_type: content[ offset ++ ],
+				image_type: content[ offset ++ ],
+				colormap_index: content[ offset ++ ] | content[ offset ++ ] << 8,
+				colormap_length: content[ offset ++ ] | content[ offset ++ ] << 8,
+				colormap_size: content[ offset ++ ],
+				origin: [
+					content[ offset ++ ] | content[ offset ++ ] << 8,
+					content[ offset ++ ] | content[ offset ++ ] << 8
+				],
+				width: content[ offset ++ ] | content[ offset ++ ] << 8,
+				height: content[ offset ++ ] | content[ offset ++ ] << 8,
+				pixel_size: content[ offset ++ ],
+				flags: content[ offset ++ ]
+			};
+
+			// check tga if it is valid format
+
+		tgaCheckHeader( header );
+
+		if ( header.id_length + offset > buffer.length ) {
+
+			console.error( 'THREE.TGALoader: No data.' );
+
+		}
+
+		// skip the needn't data
+
+		offset += header.id_length;
+
+		// get targa information about RLE compression and palette
+
+		var use_rle = false,
+			use_pal = false,
+			use_grey = false;
+
+		switch ( header.image_type ) {
+
+			case TGA_TYPE_RLE_INDEXED:
+				use_rle = true;
+				use_pal = true;
+				break;
+
+			case TGA_TYPE_INDEXED:
+				use_pal = true;
+				break;
+
+			case TGA_TYPE_RLE_RGB:
+				use_rle = true;
+				break;
+
+			case TGA_TYPE_RGB:
+				break;
+
+			case TGA_TYPE_RLE_GREY:
+				use_rle = true;
+				use_grey = true;
+				break;
+
+			case TGA_TYPE_GREY:
+				use_grey = true;
+				break;
+
+		}
+
+		//
+
+		var useOffscreen = typeof OffscreenCanvas !== 'undefined';
+
+		var canvas = useOffscreen ? new OffscreenCanvas( header.width, header.height ) : document.createElement( 'canvas' );
+		canvas.width = header.width;
+		canvas.height = header.height;
+
+		var context = canvas.getContext( '2d' );
+		var imageData = context.createImageData( header.width, header.height );
+
+		var result = tgaParse( use_rle, use_pal, header, offset, content );
+		var rgbaData = getTgaRGBA( imageData.data, header.width, header.height, result.pixel_data, result.palettes );
+
+		context.putImageData( imageData, 0, 0 );
+
+		return useOffscreen ? canvas.transferToImageBitmap() : canvas;
+
+	},
+
+	setPath: function ( value ) {
+
+		this.path = value;
+		return this;
+
+	}
+
+};
+
+export { TGALoader };

+ 19 - 0
examples/jsm/loaders/VRMLLoader.d.ts

@@ -0,0 +1,19 @@
+import {
+  Scene,
+  LoadingManager
+} from '../../../src/Three';
+
+export class VRMLLoader {
+  constructor(manager?: LoadingManager);
+  manager: LoadingManager;
+  path: string;
+  resourcePath: string;
+  crossOrigin: string;
+
+  load(url: string, onLoad: (scene: Scene) => void, onProgress?: (event: ProgressEvent) => void, onError?: (event: ErrorEvent) => void) : void;
+  setPath(path: string) : this;
+  setResourcePath(path: string) : this;
+  setCrossOrigin(path: string) : this;
+
+  parse(data: string, path: string) : Scene;
+}

+ 1360 - 0
examples/jsm/loaders/VRMLLoader.js

@@ -0,0 +1,1360 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+import {
+	AmbientLight,
+	BackSide,
+	BoxBufferGeometry,
+	BufferAttribute,
+	BufferGeometry,
+	Color,
+	CylinderBufferGeometry,
+	DefaultLoadingManager,
+	DoubleSide,
+	FileLoader,
+	Float32BufferAttribute,
+	LoaderUtils,
+	Mesh,
+	MeshBasicMaterial,
+	MeshPhongMaterial,
+	Object3D,
+	PointLight,
+	Scene,
+	SphereBufferGeometry,
+	SpotLight,
+	TextureLoader,
+	Vector3,
+	VertexColors
+} from "../../../build/three.module.js";
+
+var VRMLLoader = function ( manager ) {
+
+	this.manager = ( manager !== undefined ) ? manager : DefaultLoadingManager;
+
+};
+
+VRMLLoader.prototype = {
+
+	constructor: VRMLLoader,
+
+	// for IndexedFaceSet support
+	isRecordingPoints: false,
+	isRecordingFaces: false,
+	points: [],
+	indexes: [],
+
+	// for Background support
+	isRecordingAngles: false,
+	isRecordingColors: false,
+	angles: [],
+	colors: [],
+
+	recordingFieldname: null,
+
+	crossOrigin: 'anonymous',
+
+	load: function ( url, onLoad, onProgress, onError ) {
+
+		var scope = this;
+
+		var path = ( scope.path === undefined ) ? LoaderUtils.extractUrlBase( url ) : scope.path;
+
+		var loader = new FileLoader( this.manager );
+		loader.setPath( scope.path );
+		loader.load( url, function ( text ) {
+
+			onLoad( scope.parse( text, path ) );
+
+		}, onProgress, onError );
+
+	},
+
+	setPath: function ( value ) {
+
+		this.path = value;
+		return this;
+
+	},
+
+	setResourcePath: function ( value ) {
+
+		this.resourcePath = value;
+		return this;
+
+	},
+
+	setCrossOrigin: function ( value ) {
+
+		this.crossOrigin = value;
+		return this;
+
+	},
+
+	parse: function ( data, path ) {
+
+		var scope = this;
+
+		var textureLoader = new TextureLoader( this.manager );
+		textureLoader.setPath( this.resourcePath || path ).setCrossOrigin( this.crossOrigin );
+
+		function parseV2( lines, scene ) {
+
+			var defines = {};
+			var float_pattern = /(\b|\-|\+)([\d\.e]+)/;
+			var float2_pattern = /([\d\.\+\-e]+)\s+([\d\.\+\-e]+)/g;
+			var float3_pattern = /([\d\.\+\-e]+)\s+([\d\.\+\-e]+)\s+([\d\.\+\-e]+)/g;
+
+			/**
+			 * Vertically paints the faces interpolating between the
+			 * specified colors at the specified angels. This is used for the Background
+			 * node, but could be applied to other nodes with multiple faces as well.
+			 *
+			 * When used with the Background node, default is directionIsDown is true if
+			 * interpolating the skyColor down from the Zenith. When interpolationg up from
+			 * the Nadir i.e. interpolating the groundColor, the directionIsDown is false.
+			 *
+			 * The first angle is never specified, it is the Zenith (0 rad). Angles are specified
+			 * in radians. The geometry is thought a sphere, but could be anything. The color interpolation
+			 * is linear along the Y axis in any case.
+			 *
+			 * You must specify one more color than you have angles at the beginning of the colors array.
+			 * This is the color of the Zenith (the top of the shape).
+			 *
+			 * @param geometry
+			 * @param radius
+			 * @param angles
+			 * @param colors
+			 * @param boolean topDown Whether to work top down or bottom up.
+			 */
+			function paintFaces( geometry, radius, angles, colors, topDown ) {
+
+				var direction = ( topDown === true ) ? 1 : - 1;
+
+				var coord = [], A = {}, B = {}, applyColor = false;
+
+				for ( var k = 0; k < angles.length; k ++ ) {
+
+					// push the vector at which the color changes
+
+					var vec = {
+						x: direction * ( Math.cos( angles[ k ] ) * radius ),
+						y: direction * ( Math.sin( angles[ k ] ) * radius )
+					};
+
+					coord.push( vec );
+
+				}
+
+				var index = geometry.index;
+				var positionAttribute = geometry.attributes.position;
+				var colorAttribute = new BufferAttribute( new Float32Array( geometry.attributes.position.count * 3 ), 3 );
+
+				var position = new Vector3();
+				var color = new Color();
+
+				for ( var i = 0; i < index.count; i ++ ) {
+
+					var vertexIndex = index.getX( i );
+
+					position.fromBufferAttribute( positionAttribute, vertexIndex );
+
+					for ( var j = 0; j < colors.length; j ++ ) {
+
+						// linear interpolation between aColor and bColor, calculate proportion
+						// A is previous point (angle)
+
+						if ( j === 0 ) {
+
+							A.x = 0;
+							A.y = ( topDown === true ) ? radius : - 1 * radius;
+
+						} else {
+
+							A.x = coord[ j - 1 ].x;
+							A.y = coord[ j - 1 ].y;
+
+						}
+
+						// B is current point (angle)
+
+						B = coord[ j ];
+
+						if ( B !== undefined ) {
+
+							// p has to be between the points A and B which we interpolate
+
+							applyColor = ( topDown === true ) ? ( position.y <= A.y && position.y > B.y ) : ( position.y >= A.y && position.y < B.y );
+
+							if ( applyColor === true ) {
+
+								var aColor = colors[ j ];
+								var bColor = colors[ j + 1 ];
+
+								// below is simple linear interpolation
+
+								var t = Math.abs( position.y - A.y ) / ( A.y - B.y );
+
+								// to make it faster, you can only calculate this if the y coord changes, the color is the same for points with the same y
+
+								color.copy( aColor ).lerp( bColor, t );
+
+								colorAttribute.setXYZ( vertexIndex, color.r, color.g, color.b );
+
+							} else {
+
+								var colorIndex = ( topDown === true ) ? colors.length - 1 : 0;
+								var c = colors[ colorIndex ];
+								colorAttribute.setXYZ( vertexIndex, c.r, c.g, c.b );
+
+							}
+
+						}
+
+					}
+
+				}
+
+				geometry.addAttribute( 'color', colorAttribute );
+
+			}
+
+			var index = [];
+
+			function parseProperty( node, line ) {
+
+				var parts = [], part, property = {}, fieldName;
+
+				/**
+				 * Expression for matching relevant information, such as a name or value, but not the separators
+				 * @type {RegExp}
+				 */
+				var regex = /[^\s,\[\]]+/g;
+
+				var point;
+
+				while ( null !== ( part = regex.exec( line ) ) ) {
+
+					parts.push( part[ 0 ] );
+
+				}
+
+				fieldName = parts[ 0 ];
+
+
+				// trigger several recorders
+				switch ( fieldName ) {
+
+					case 'skyAngle':
+					case 'groundAngle':
+						scope.recordingFieldname = fieldName;
+						scope.isRecordingAngles = true;
+						scope.angles = [];
+						break;
+
+					case 'color':
+					case 'skyColor':
+					case 'groundColor':
+						scope.recordingFieldname = fieldName;
+						scope.isRecordingColors = true;
+						scope.colors = [];
+						break;
+
+					case 'point':
+					case 'vector':
+						scope.recordingFieldname = fieldName;
+						scope.isRecordingPoints = true;
+						scope.points = [];
+						break;
+
+					case 'colorIndex':
+					case 'coordIndex':
+					case 'normalIndex':
+					case 'texCoordIndex':
+						scope.recordingFieldname = fieldName;
+						scope.isRecordingFaces = true;
+						scope.indexes = [];
+						break;
+
+				}
+
+				if ( scope.isRecordingFaces ) {
+
+					// the parts hold the indexes as strings
+					if ( parts.length > 0 ) {
+
+						for ( var ind = 0; ind < parts.length; ind ++ ) {
+
+							// the part should either be positive integer or -1
+							if ( ! /(-?\d+)/.test( parts[ ind ] ) ) {
+
+								continue;
+
+							}
+
+							// end of current face
+							if ( parts[ ind ] === '-1' ) {
+
+								if ( index.length > 0 ) {
+
+									scope.indexes.push( index );
+
+								}
+
+								// start new one
+								index = [];
+
+							} else {
+
+								index.push( parseInt( parts[ ind ] ) );
+
+							}
+
+						}
+
+					}
+
+					// end
+					if ( /]/.exec( line ) ) {
+
+						if ( index.length > 0 ) {
+
+							scope.indexes.push( index );
+
+						}
+
+						// start new one
+						index = [];
+
+						scope.isRecordingFaces = false;
+						node[ scope.recordingFieldname ] = scope.indexes;
+
+					}
+
+				} else if ( scope.isRecordingPoints ) {
+
+					if ( node.nodeType == 'Coordinate' ) {
+
+						while ( null !== ( parts = float3_pattern.exec( line ) ) ) {
+
+							point = {
+								x: parseFloat( parts[ 1 ] ),
+								y: parseFloat( parts[ 2 ] ),
+								z: parseFloat( parts[ 3 ] )
+							};
+
+							scope.points.push( point );
+
+						}
+
+					}
+
+					if ( node.nodeType == 'Normal' ) {
+
+  						while ( null !== ( parts = float3_pattern.exec( line ) ) ) {
+
+							point = {
+								x: parseFloat( parts[ 1 ] ),
+								y: parseFloat( parts[ 2 ] ),
+								z: parseFloat( parts[ 3 ] )
+							};
+
+							scope.points.push( point );
+
+						}
+
+					}
+
+					if ( node.nodeType == 'TextureCoordinate' ) {
+
+						while ( null !== ( parts = float2_pattern.exec( line ) ) ) {
+
+							point = {
+								x: parseFloat( parts[ 1 ] ),
+								y: parseFloat( parts[ 2 ] )
+							};
+
+							scope.points.push( point );
+
+						}
+
+					}
+
+					// end
+					if ( /]/.exec( line ) ) {
+
+						scope.isRecordingPoints = false;
+						node.points = scope.points;
+
+					}
+
+				} else if ( scope.isRecordingAngles ) {
+
+					// the parts hold the angles as strings
+					if ( parts.length > 0 ) {
+
+						for ( var ind = 0; ind < parts.length; ind ++ ) {
+
+							// the part should be a float
+							if ( ! float_pattern.test( parts[ ind ] ) ) {
+
+								continue;
+
+							}
+
+							scope.angles.push( parseFloat( parts[ ind ] ) );
+
+						}
+
+					}
+
+					// end
+					if ( /]/.exec( line ) ) {
+
+						scope.isRecordingAngles = false;
+						node[ scope.recordingFieldname ] = scope.angles;
+
+					}
+
+				} else if ( scope.isRecordingColors ) {
+
+					while ( null !== ( parts = float3_pattern.exec( line ) ) ) {
+
+						var color = {
+							r: parseFloat( parts[ 1 ] ),
+							g: parseFloat( parts[ 2 ] ),
+							b: parseFloat( parts[ 3 ] )
+						};
+
+						scope.colors.push( color );
+
+					}
+
+					// end
+					if ( /]/.exec( line ) ) {
+
+						scope.isRecordingColors = false;
+						node[ scope.recordingFieldname ] = scope.colors;
+
+					}
+
+				} else if ( parts[ parts.length - 1 ] !== 'NULL' && fieldName !== 'children' ) {
+
+					switch ( fieldName ) {
+
+						case 'diffuseColor':
+						case 'emissiveColor':
+						case 'specularColor':
+						case 'color':
+
+							if ( parts.length !== 4 ) {
+
+								console.warn( 'THREE.VRMLLoader: Invalid color format detected for %s.', fieldName );
+								break;
+
+							}
+
+							property = {
+								r: parseFloat( parts[ 1 ] ),
+								g: parseFloat( parts[ 2 ] ),
+								b: parseFloat( parts[ 3 ] )
+							};
+
+							break;
+
+						case 'location':
+						case 'direction':
+						case 'translation':
+						case 'scale':
+						case 'size':
+							if ( parts.length !== 4 ) {
+
+								console.warn( 'THREE.VRMLLoader: Invalid vector format detected for %s.', fieldName );
+								break;
+
+							}
+
+							property = {
+								x: parseFloat( parts[ 1 ] ),
+								y: parseFloat( parts[ 2 ] ),
+								z: parseFloat( parts[ 3 ] )
+							};
+
+							break;
+
+						case 'intensity':
+						case 'cutOffAngle':
+						case 'radius':
+						case 'topRadius':
+						case 'bottomRadius':
+						case 'height':
+						case 'transparency':
+						case 'shininess':
+						case 'ambientIntensity':
+						case 'creaseAngle':
+							if ( parts.length !== 2 ) {
+
+								console.warn( 'THREE.VRMLLoader: Invalid single float value specification detected for %s.', fieldName );
+								break;
+
+							}
+
+							property = parseFloat( parts[ 1 ] );
+
+							break;
+
+						case 'rotation':
+							if ( parts.length !== 5 ) {
+
+								console.warn( 'THREE.VRMLLoader: Invalid quaternion format detected for %s.', fieldName );
+								break;
+
+							}
+
+							property = {
+								x: parseFloat( parts[ 1 ] ),
+								y: parseFloat( parts[ 2 ] ),
+								z: parseFloat( parts[ 3 ] ),
+								w: parseFloat( parts[ 4 ] )
+							};
+
+							break;
+
+						case 'on':
+						case 'ccw':
+						case 'solid':
+						case 'colorPerVertex':
+						case 'convex':
+							if ( parts.length !== 2 ) {
+
+								console.warn( 'THREE.VRMLLoader: Invalid format detected for %s.', fieldName );
+								break;
+
+							}
+
+							property = parts[ 1 ] === 'TRUE' ? true : false;
+
+							break;
+
+					}
+
+					// VRMLLoader does not support text so it can't process the "string" property yet
+
+					if ( fieldName !== 'string' ) node[ fieldName ] = property;
+
+				}
+
+				return property;
+
+			}
+
+			function getTree( lines ) {
+
+				var tree = { 'string': 'Scene', children: [] };
+				var current = tree;
+				var matches;
+				var specification;
+
+				for ( var i = 0; i < lines.length; i ++ ) {
+
+					var comment = '';
+
+					var line = lines[ i ];
+
+					// omit whitespace only lines
+					if ( null !== ( /^\s+?$/g.exec( line ) ) ) {
+
+						continue;
+
+					}
+
+					line = line.trim();
+
+					// skip empty lines
+					if ( line === '' ) {
+
+						continue;
+
+					}
+
+					if ( /#/.exec( line ) ) {
+
+						var parts = line.split( '#' );
+
+						// discard everything after the #, it is a comment
+						line = parts[ 0 ];
+
+						// well, let's also keep the comment
+						comment = parts[ 1 ];
+
+					}
+
+					if ( matches = /([^\s]*){1}(?:\s+)?{/.exec( line ) ) {
+
+						// first subpattern should match the Node name
+
+						var block = { 'nodeType': matches[ 1 ], 'string': line, 'parent': current, 'children': [], 'comment': comment };
+						current.children.push( block );
+						current = block;
+
+						if ( /}/.exec( line ) ) {
+
+							// example: geometry Box { size 1 1 1 } # all on the same line
+							specification = /{(.*)}/.exec( line )[ 1 ];
+
+							// todo: remove once new parsing is complete?
+							block.children.push( specification );
+
+							parseProperty( current, specification );
+
+							current = current.parent;
+
+						}
+
+					} else if ( /}/.exec( line ) ) {
+
+						current = current.parent;
+
+					} else if ( line !== '' ) {
+
+						parseProperty( current, line );
+						// todo: remove once new parsing is complete? we still do not parse geometry and appearance the new way
+						current.children.push( line );
+
+					}
+
+				}
+
+				return tree;
+
+			}
+
+			function parseNode( data, parent ) {
+
+				var object;
+
+				if ( typeof data === 'string' ) {
+
+					if ( /USE/.exec( data ) ) {
+
+						var defineKey = /USE\s+?([^\s]+)/.exec( data )[ 1 ];
+
+						if ( undefined == defines[ defineKey ] ) {
+
+							console.warn( 'THREE.VRMLLoader: %s is not defined.', defineKey );
+
+						} else {
+
+							if ( /appearance/.exec( data ) && defineKey ) {
+
+								parent.material = defines[ defineKey ].clone();
+
+							} else if ( /geometry/.exec( data ) && defineKey ) {
+
+								parent.geometry = defines[ defineKey ].clone();
+
+								// the solid property is not cloned with clone(), is only needed for VRML loading, so we need to transfer it
+								if ( defines[ defineKey ].solid !== undefined && defines[ defineKey ].solid === false ) {
+
+									parent.geometry.solid = false;
+									parent.material.side = DoubleSide;
+
+								}
+
+							} else if ( defineKey ) {
+
+								object = defines[ defineKey ].clone();
+								parent.add( object );
+
+							}
+
+						}
+
+					}
+
+					return;
+
+				}
+
+				object = parent;
+
+				if ( data.string.indexOf( 'AmbientLight' ) > - 1 && data.nodeType === 'PointLight' ) {
+
+					data.nodeType = 'AmbientLight';
+
+				}
+
+				var l_visible = data.on !== undefined ? data.on : true;
+				var l_intensity = data.intensity !== undefined ? data.intensity : 1;
+				var l_color = new Color();
+
+				if ( data.color ) {
+
+					l_color.copy( data.color );
+
+				}
+
+				if ( data.nodeType === 'AmbientLight' ) {
+
+					object = new AmbientLight( l_color, l_intensity );
+					object.visible = l_visible;
+
+					parent.add( object );
+
+				} else if ( data.nodeType === 'PointLight' ) {
+
+					var l_distance = 0;
+
+					if ( data.radius !== undefined && data.radius < 1000 ) {
+
+						l_distance = data.radius;
+
+					}
+
+					object = new PointLight( l_color, l_intensity, l_distance );
+					object.visible = l_visible;
+
+					parent.add( object );
+
+				} else if ( data.nodeType === 'SpotLight' ) {
+
+					var l_intensity = 1;
+					var l_distance = 0;
+					var l_angle = Math.PI / 3;
+					var l_penumbra = 0;
+					var l_visible = true;
+
+					if ( data.radius !== undefined && data.radius < 1000 ) {
+
+						l_distance = data.radius;
+
+					}
+
+					if ( data.cutOffAngle !== undefined ) {
+
+						l_angle = data.cutOffAngle;
+
+					}
+
+					object = new SpotLight( l_color, l_intensity, l_distance, l_angle, l_penumbra );
+					object.visible = l_visible;
+
+					parent.add( object );
+
+				} else if ( data.nodeType === 'Transform' || data.nodeType === 'Group' ) {
+
+					object = new Object3D();
+
+					if ( /DEF/.exec( data.string ) ) {
+
+						object.name = /DEF\s+([^\s]+)/.exec( data.string )[ 1 ];
+						defines[ object.name ] = object;
+
+					}
+
+					if ( data.translation !== undefined ) {
+
+						var t = data.translation;
+
+						object.position.set( t.x, t.y, t.z );
+
+					}
+
+					if ( data.rotation !== undefined ) {
+
+						var r = data.rotation;
+
+						object.quaternion.setFromAxisAngle( new Vector3( r.x, r.y, r.z ), r.w );
+
+					}
+
+					if ( data.scale !== undefined ) {
+
+						var s = data.scale;
+
+						object.scale.set( s.x, s.y, s.z );
+
+					}
+
+					parent.add( object );
+
+				} else if ( data.nodeType === 'Shape' ) {
+
+					object = new Mesh();
+
+					if ( /DEF/.exec( data.string ) ) {
+
+						object.name = /DEF\s+([^\s]+)/.exec( data.string )[ 1 ];
+
+						defines[ object.name ] = object;
+
+					}
+
+					parent.add( object );
+
+				} else if ( data.nodeType === 'Background' ) {
+
+					var segments = 20;
+
+					// sky (full sphere):
+
+					var radius = 2e4;
+
+					var skyGeometry = new SphereBufferGeometry( radius, segments, segments );
+					var skyMaterial = new MeshBasicMaterial( { fog: false, side: BackSide } );
+
+					if ( data.skyColor.length > 1 ) {
+
+						paintFaces( skyGeometry, radius, data.skyAngle, data.skyColor, true );
+
+						skyMaterial.vertexColors = VertexColors;
+
+					} else {
+
+						var color = data.skyColor[ 0 ];
+						skyMaterial.color.setRGB( color.r, color.b, color.g );
+
+					}
+
+					scene.add( new Mesh( skyGeometry, skyMaterial ) );
+
+					// ground (half sphere):
+
+					if ( data.groundColor !== undefined ) {
+
+						radius = 1.2e4;
+
+						var groundGeometry = new SphereBufferGeometry( radius, segments, segments, 0, 2 * Math.PI, 0.5 * Math.PI, 1.5 * Math.PI );
+						var groundMaterial = new MeshBasicMaterial( { fog: false, side: BackSide, vertexColors: VertexColors } );
+
+						paintFaces( groundGeometry, radius, data.groundAngle, data.groundColor, false );
+
+						scene.add( new Mesh( groundGeometry, groundMaterial ) );
+
+					}
+
+				} else if ( /geometry/.exec( data.string ) ) {
+
+					if ( data.nodeType === 'Box' ) {
+
+						var s = data.size;
+
+						parent.geometry = new BoxBufferGeometry( s.x, s.y, s.z );
+
+					} else if ( data.nodeType === 'Cylinder' ) {
+
+						parent.geometry = new CylinderBufferGeometry( data.radius, data.radius, data.height );
+
+					} else if ( data.nodeType === 'Cone' ) {
+
+						parent.geometry = new CylinderBufferGeometry( data.topRadius, data.bottomRadius, data.height );
+
+					} else if ( data.nodeType === 'Sphere' ) {
+
+						parent.geometry = new SphereBufferGeometry( data.radius );
+
+					} else if ( data.nodeType === 'IndexedLineSet' ) {
+
+						console.warn( 'THREE.VRMLLoader: IndexedLineSet not supported yet.' );
+						parent.parent.remove( parent ); // since the loader is not able to parse the geometry, remove the respective 3D object
+
+					} else if ( data.nodeType === 'Text' ) {
+
+						console.warn( 'THREE.VRMLLoader: Text not supported yet.' );
+						parent.parent.remove( parent );
+
+					} else if ( data.nodeType === 'IndexedFaceSet' ) {
+
+						var geometry = new BufferGeometry();
+
+						var positions = [];
+						var colors = [];
+						var normals = [];
+						var uvs = [];
+
+						var position, color, normal, uv;
+
+						var i, il, j, jl;
+
+						for ( i = 0, il = data.children.length; i < il; i ++ ) {
+
+							var child = data.children[ i ];
+
+							// uvs
+
+							if ( child.nodeType === 'TextureCoordinate' ) {
+
+								if ( child.points ) {
+
+									for ( j = 0, jl = child.points.length; j < jl; j ++ ) {
+
+										uv = child.points[ j ];
+										uvs.push( uv.x, uv.y );
+
+									}
+
+								}
+
+							}
+
+							// normals
+
+							if ( child.nodeType === 'Normal' ) {
+
+								if ( child.points ) {
+
+									for ( j = 0, jl = child.points.length; j < jl; j ++ ) {
+
+										normal = child.points[ j ];
+										normals.push( normal.x, normal.y, normal.z );
+
+									}
+
+								}
+
+							}
+
+							// colors
+
+							if ( child.nodeType === 'Color' ) {
+
+								if ( child.color ) {
+
+									for ( j = 0, jl = child.color.length; j < jl; j ++ ) {
+
+										color = child.color[ j ];
+										colors.push( color.r, color.g, color.b );
+
+									}
+
+								}
+
+							}
+
+							// positions
+
+							if ( child.nodeType === 'Coordinate' ) {
+
+								if ( child.points ) {
+
+									for ( j = 0, jl = child.points.length; j < jl; j ++ ) {
+
+										position = child.points[ j ];
+										positions.push( position.x, position.y, position.z );
+
+									}
+
+								}
+
+								if ( child.string.indexOf( 'DEF' ) > - 1 ) {
+
+									var name = /DEF\s+([^\s]+)/.exec( child.string )[ 1 ];
+
+									defines[ name ] = positions.slice( 0 );
+
+								}
+
+								if ( child.string.indexOf( 'USE' ) > - 1 ) {
+
+									var defineKey = /USE\s+([^\s]+)/.exec( child.string )[ 1 ];
+
+									positions = defines[ defineKey ];
+
+								}
+
+							}
+
+						}
+
+						// some shapes only have vertices for use in other shapes
+
+						if ( data.coordIndex ) {
+
+							function triangulateIndexArray( indexArray, ccw, colorPerVertex ) {
+
+								if ( ccw === undefined ) {
+
+									// ccw is true by default
+									ccw = true;
+
+								}
+
+								var triangulatedIndexArray = [];
+								var skip = 0;
+
+								for ( i = 0, il = indexArray.length; i < il; i ++ ) {
+
+									if ( colorPerVertex === false ) {
+
+										var colorIndices = indexArray[ i ];
+
+										for ( j = 0, jl = colorIndices.length; j < jl; j ++ ) {
+
+											var index = colorIndices[ j ];
+
+											triangulatedIndexArray.push( index, index, index );
+
+										}
+
+									} else {
+
+										var indexedFace = indexArray[ i ];
+
+										// VRML support multipoint indexed face sets (more then 3 vertices). You must calculate the composing triangles here
+
+										skip = 0;
+
+										while ( indexedFace.length >= 3 && skip < ( indexedFace.length - 2 ) ) {
+
+											var i1 = indexedFace[ 0 ];
+											var i2 = indexedFace[ skip + ( ccw ? 1 : 2 ) ];
+											var i3 = indexedFace[ skip + ( ccw ? 2 : 1 ) ];
+
+											triangulatedIndexArray.push( i1, i2, i3 );
+
+											skip ++;
+
+										}
+
+									}
+
+								}
+
+								return triangulatedIndexArray;
+
+							}
+
+							var positionIndexes = data.coordIndex ? triangulateIndexArray( data.coordIndex, data.ccw ) : [];
+							var normalIndexes = data.normalIndex ? triangulateIndexArray( data.normalIndex, data.ccw ) : positionIndexes;
+							var colorIndexes = data.colorIndex ? triangulateIndexArray( data.colorIndex, data.ccw, data.colorPerVertex ) : [];
+							var uvIndexes = data.texCoordIndex ? triangulateIndexArray( data.texCoordIndex, data.ccw ) : positionIndexes;
+
+							var newIndexes = [];
+							var newPositions = [];
+							var newNormals = [];
+							var newColors = [];
+							var newUvs = [];
+
+							// if any other index array does not match the coordinate indexes, split any points that differ
+
+							var pointMap = Object.create( null );
+
+							for ( i = 0; i < positionIndexes.length; i ++ ) {
+
+								var pointAttributes = [];
+
+								var positionIndex = positionIndexes[ i ];
+								var normalIndex = normalIndexes[ i ];
+								var colorIndex = colorIndexes[ i ];
+								var uvIndex = uvIndexes[ i ];
+
+								var base = 10; // which base to use to represent each value
+
+								pointAttributes.push( positionIndex.toString( base ) );
+
+								if ( normalIndex !== undefined ) {
+
+									pointAttributes.push( normalIndex.toString( base ) );
+
+								}
+
+								if ( colorIndex !== undefined ) {
+
+									pointAttributes.push( colorIndex.toString( base ) );
+
+								}
+
+								if ( uvIndex !== undefined ) {
+
+									pointAttributes.push( uvIndex.toString( base ) );
+
+								}
+
+								var pointId = pointAttributes.join( ',' );
+								var newIndex = pointMap[ pointId ];
+
+								if ( newIndex === undefined ) {
+
+									newIndex = newPositions.length / 3;
+									pointMap[ pointId ] = newIndex;
+
+									newPositions.push(
+										positions[ positionIndex * 3 ],
+										positions[ positionIndex * 3 + 1 ],
+										positions[ positionIndex * 3 + 2 ]
+									);
+
+									if ( normalIndex !== undefined && normals.length > 0 ) {
+
+										newNormals.push(
+											normals[ normalIndex * 3 ],
+											normals[ normalIndex * 3 + 1 ],
+											normals[ normalIndex * 3 + 2 ]
+										);
+
+									}
+
+									if ( colorIndex !== undefined && colors.length > 0 ) {
+
+										newColors.push(
+											colors[ colorIndex * 3 ],
+											colors[ colorIndex * 3 + 1 ],
+											colors[ colorIndex * 3 + 2 ]
+										);
+
+									}
+
+									if ( uvIndex !== undefined && uvs.length > 0 ) {
+
+										newUvs.push(
+											uvs[ uvIndex * 2 ],
+											uvs[ uvIndex * 2 + 1 ]
+										);
+
+									}
+
+								}
+
+								newIndexes.push( newIndex );
+
+							}
+
+							positions = newPositions;
+							normals = newNormals;
+							colors = newColors;
+							uvs = newUvs;
+
+							geometry.setIndex( newIndexes );
+
+						} else {
+
+							// do not add dummy mesh to the scene
+
+							parent.parent.remove( parent );
+
+						}
+
+						if ( false === data.solid ) {
+
+							parent.material.side = DoubleSide;
+
+						}
+
+						// we need to store it on the geometry for use with defines
+						geometry.solid = data.solid;
+
+						geometry.addAttribute( 'position', new Float32BufferAttribute( positions, 3 ) );
+
+						if ( colors.length > 0 ) {
+
+							geometry.addAttribute( 'color', new Float32BufferAttribute( colors, 3 ) );
+
+							parent.material.vertexColors = VertexColors;
+
+						}
+
+						if ( uvs.length > 0 ) {
+
+							geometry.addAttribute( 'uv', new Float32BufferAttribute( uvs, 2 ) );
+
+						}
+
+						if ( normals.length > 0 ) {
+
+							geometry.addAttribute( 'normal', new Float32BufferAttribute( normals, 3 ) );
+
+						} else {
+
+							// convert geometry to non-indexed to get sharp normals
+							geometry = geometry.toNonIndexed();
+							geometry.computeVertexNormals();
+
+						}
+
+						geometry.computeBoundingSphere();
+
+						// see if it's a define
+						if ( /DEF/.exec( data.string ) ) {
+
+							geometry.name = /DEF ([^\s]+)/.exec( data.string )[ 1 ];
+							defines[ geometry.name ] = geometry;
+
+						}
+
+						parent.geometry = geometry;
+
+					}
+
+					return;
+
+				} else if ( /appearance/.exec( data.string ) ) {
+
+					for ( var i = 0; i < data.children.length; i ++ ) {
+
+						var child = data.children[ i ];
+
+						if ( child.nodeType === 'Material' ) {
+
+							var material = new MeshPhongMaterial();
+
+							if ( child.diffuseColor !== undefined ) {
+
+								var d = child.diffuseColor;
+
+								material.color.setRGB( d.r, d.g, d.b );
+
+							}
+
+							if ( child.emissiveColor !== undefined ) {
+
+								var e = child.emissiveColor;
+
+								material.emissive.setRGB( e.r, e.g, e.b );
+
+							}
+
+							if ( child.specularColor !== undefined ) {
+
+								var s = child.specularColor;
+
+								material.specular.setRGB( s.r, s.g, s.b );
+
+							}
+
+							if ( child.transparency !== undefined ) {
+
+								var t = child.transparency;
+
+								// transparency is opposite of opacity
+								material.opacity = Math.abs( 1 - t );
+
+								material.transparent = true;
+
+							}
+
+							if ( /DEF/.exec( data.string ) ) {
+
+								material.name = /DEF ([^\s]+)/.exec( data.string )[ 1 ];
+
+								defines[ material.name ] = material;
+
+							}
+
+							parent.material = material;
+
+						}
+
+						if ( child.nodeType === 'ImageTexture' ) {
+
+							var textureName = /"([^"]+)"/.exec( child.children[ 0 ] );
+
+							if ( textureName ) {
+
+								parent.material.name = textureName[ 1 ];
+
+								parent.material.map = textureLoader.load( textureName[ 1 ] );
+
+							}
+
+						}
+
+					}
+
+					return;
+
+				}
+
+				for ( var i = 0, l = data.children.length; i < l; i ++ ) {
+
+					parseNode( data.children[ i ], object );
+
+				}
+
+			}
+
+			parseNode( getTree( lines ), scene );
+
+		}
+
+		var scene = new Scene();
+
+		var lines = data.split( '\n' );
+
+		// some lines do not have breaks
+
+		for ( var i = lines.length - 1; i > 0; i -- ) {
+
+			// The # symbol indicates that all subsequent text, until the end of the line is a comment,
+			// and should be ignored. (see http://gun.teipir.gr/VRML-amgem/spec/part1/grammar.html)
+			lines[ i ] = lines[ i ].replace( /(#.*)/, '' );
+
+			var line = lines[ i ];
+
+			// split lines with {..{ or {..[ - some have both
+			if ( /{.*[{\[]/.test( line ) ) {
+
+				var parts = line.split( '{' ).join( '{\n' ).split( '\n' );
+				parts.unshift( 1 );
+				parts.unshift( i );
+				lines.splice.apply( lines, parts );
+
+			} else if ( /\].*}/.test( line ) ) {
+
+				// split lines with ]..}
+				var parts = line.split( ']' ).join( ']\n' ).split( '\n' );
+				parts.unshift( 1 );
+				parts.unshift( i );
+				lines.splice.apply( lines, parts );
+
+			}
+
+			line = lines[ i ];
+
+			if ( /}.*}/.test( line ) ) {
+
+				// split lines with }..}
+				var parts = line.split( '}' ).join( '}\n' ).split( '\n' );
+				parts.unshift( 1 );
+				parts.unshift( i );
+				lines.splice.apply( lines, parts );
+
+			}
+
+			line = lines[ i ];
+
+			if ( /^\b[^\s]+\b$/.test( line.trim() ) ) {
+
+				// prevent lines with single words like "coord" or "geometry", see #12209
+				lines[ i + 1 ] = line + ' ' + lines[ i + 1 ].trim();
+				lines.splice( i, 1 );
+
+			} else if ( ( line.indexOf( 'coord' ) > - 1 ) && ( line.indexOf( '[' ) < 0 ) && ( line.indexOf( '{' ) < 0 ) ) {
+
+				// force the parser to create Coordinate node for empty coords
+				// coord USE something -> coord USE something Coordinate {}
+
+				lines[ i ] += ' Coordinate {}';
+
+			}
+
+		}
+
+		var header = lines.shift();
+
+		if ( /V1.0/.exec( header ) ) {
+
+			console.warn( 'THREE.VRMLLoader: V1.0 not supported yet.' );
+
+		} else if ( /V2.0/.exec( header ) ) {
+
+			parseV2( lines, scene );
+
+		}
+
+		return scene;
+
+	}
+
+};
+
+export { VRMLLoader };

+ 19 - 0
examples/jsm/renderers/CSS2DRenderer.d.ts

@@ -0,0 +1,19 @@
+import {
+	Object3D,
+  Scene,
+	Camera
+} from '../../../src/Three';
+
+export class CSS2DObject extends Object3D {
+  constructor(element: HTMLElement);
+  element: HTMLElement;
+}
+
+export class CSS2DRenderer {
+  constructor();
+  domElement: HTMLElement;
+
+  getSize(): {width: number, height: number};
+  setSize(width: number, height: number): void;
+  render(scene: Scene, camera: Camera): void;
+}

+ 184 - 0
examples/jsm/renderers/CSS2DRenderer.js

@@ -0,0 +1,184 @@
+/**
+ * @author mrdoob / http://mrdoob.com/
+ */
+
+import {
+	Matrix4,
+	Object3D,
+	REVISION,
+	Vector3
+} from "../../../build/three.module.js";
+
+var CSS2DObject = function ( element ) {
+
+	Object3D.call( this );
+
+	this.element = element;
+	this.element.style.position = 'absolute';
+
+	this.addEventListener( 'removed', function () {
+
+		if ( this.element.parentNode !== null ) {
+
+			this.element.parentNode.removeChild( this.element );
+
+		}
+
+	} );
+
+};
+
+CSS2DObject.prototype = Object.create( Object3D.prototype );
+CSS2DObject.prototype.constructor = CSS2DObject;
+
+//
+
+var CSS2DRenderer = function () {
+
+	console.log( 'THREE.CSS2DRenderer', REVISION );
+
+	var _width, _height;
+	var _widthHalf, _heightHalf;
+
+	var vector = new Vector3();
+	var viewMatrix = new Matrix4();
+	var viewProjectionMatrix = new Matrix4();
+
+	var cache = {
+		objects: new WeakMap()
+	};
+
+	var domElement = document.createElement( 'div' );
+	domElement.style.overflow = 'hidden';
+
+	this.domElement = domElement;
+
+	this.getSize = function () {
+
+		return {
+			width: _width,
+			height: _height
+		};
+
+	};
+
+	this.setSize = function ( width, height ) {
+
+		_width = width;
+		_height = height;
+
+		_widthHalf = _width / 2;
+		_heightHalf = _height / 2;
+
+		domElement.style.width = width + 'px';
+		domElement.style.height = height + 'px';
+
+	};
+
+	var renderObject = function ( object, camera ) {
+
+		if ( object instanceof CSS2DObject ) {
+
+			vector.setFromMatrixPosition( object.matrixWorld );
+			vector.applyMatrix4( viewProjectionMatrix );
+
+			var element = object.element;
+			var style = 'translate(-50%,-50%) translate(' + ( vector.x * _widthHalf + _widthHalf ) + 'px,' + ( - vector.y * _heightHalf + _heightHalf ) + 'px)';
+
+			element.style.WebkitTransform = style;
+			element.style.MozTransform = style;
+			element.style.oTransform = style;
+			element.style.transform = style;
+
+			element.style.display = ( object.visible && vector.z >= - 1 && vector.z <= 1 ) ? '' : 'none';
+
+			var objectData = {
+				distanceToCameraSquared: getDistanceToSquared( camera, object )
+			};
+
+			cache.objects.set( object, objectData );
+
+			if ( element.parentNode !== domElement ) {
+
+				domElement.appendChild( element );
+
+			}
+
+		}
+
+		for ( var i = 0, l = object.children.length; i < l; i ++ ) {
+
+			renderObject( object.children[ i ], camera );
+
+		}
+
+	};
+
+	var getDistanceToSquared = function () {
+
+		var a = new Vector3();
+		var b = new Vector3();
+
+		return function ( object1, object2 ) {
+
+			a.setFromMatrixPosition( object1.matrixWorld );
+			b.setFromMatrixPosition( object2.matrixWorld );
+
+			return a.distanceToSquared( b );
+
+		};
+
+	}();
+
+	var filterAndFlatten = function ( scene ) {
+
+		var result = [];
+
+		scene.traverse( function ( object ) {
+
+			if ( object instanceof CSS2DObject ) result.push( object );
+
+		} );
+
+		return result;
+
+	};
+
+	var zOrder = function ( scene ) {
+
+		var sorted = filterAndFlatten( scene ).sort( function ( a, b ) {
+
+			var distanceA = cache.objects.get( a ).distanceToCameraSquared;
+			var distanceB = cache.objects.get( b ).distanceToCameraSquared;
+
+			return distanceA - distanceB;
+
+		} );
+
+		var zMax = sorted.length;
+
+		for ( var i = 0, l = sorted.length; i < l; i ++ ) {
+
+			sorted[ i ].element.style.zIndex = zMax - i;
+
+		}
+
+	};
+
+	this.render = function ( scene, camera ) {
+
+		scene.updateMatrixWorld();
+
+		if ( camera.parent === null ) camera.updateMatrixWorld();
+
+		viewMatrix.copy( camera.matrixWorldInverse );
+		viewProjectionMatrix.multiplyMatrices( camera.projectionMatrix, viewMatrix );
+
+		renderObject( scene, camera );
+		zOrder( scene );
+
+	};
+
+};
+
+export { CSS2DObject, CSS2DRenderer };

+ 23 - 0
examples/jsm/renderers/CSS3DRenderer.d.ts

@@ -0,0 +1,23 @@
+import {
+	Object3D,
+  Scene,
+	Camera
+} from '../../../src/Three';
+
+export class CSS3DObject extends Object3D {
+  constructor(element: HTMLElement);
+  element: HTMLElement;
+}
+
+export class CSS3DSprite extends CSS3DObject {
+  constructor(element: HTMLElement);
+}
+
+export class CSS3DRenderer {
+  constructor();
+  domElement: HTMLElement;
+
+  getSize(): {width: number, height: number};
+  setSize(width: number, height: number): void;
+  render(scene: Scene, camera: Camera): void;
+}

+ 340 - 0
examples/jsm/renderers/CSS3DRenderer.js

@@ -0,0 +1,340 @@
+/**
+ * Based on http://www.emagix.net/academic/mscs-project/item/camera-sync-with-css3-and-webgl-threejs
+ * @author mrdoob / http://mrdoob.com/
+ * @author yomotsu / https://yomotsu.net/
+ */
+
+import {
+	Matrix4,
+	Object3D,
+	REVISION,
+	Vector3
+} from "../../../build/three.module.js";
+
+var CSS3DObject = function ( element ) {
+
+	Object3D.call( this );
+
+	this.element = element;
+	this.element.style.position = 'absolute';
+
+	this.addEventListener( 'removed', function () {
+
+		if ( this.element.parentNode !== null ) {
+
+			this.element.parentNode.removeChild( this.element );
+
+		}
+
+	} );
+
+};
+
+CSS3DObject.prototype = Object.create( Object3D.prototype );
+CSS3DObject.prototype.constructor = CSS3DObject;
+
+var CSS3DSprite = function ( element ) {
+
+	CSS3DObject.call( this, element );
+
+};
+
+CSS3DSprite.prototype = Object.create( CSS3DObject.prototype );
+CSS3DSprite.prototype.constructor = CSS3DSprite;
+
+//
+
+var CSS3DRenderer = function () {
+
+	console.log( 'THREE.CSS3DRenderer', REVISION );
+
+	var _width, _height;
+	var _widthHalf, _heightHalf;
+
+	var matrix = new Matrix4();
+
+	var cache = {
+		camera: { fov: 0, style: '' },
+		objects: new WeakMap()
+	};
+
+	var domElement = document.createElement( 'div' );
+	domElement.style.overflow = 'hidden';
+
+	this.domElement = domElement;
+
+	var cameraElement = document.createElement( 'div' );
+
+	cameraElement.style.WebkitTransformStyle = 'preserve-3d';
+	cameraElement.style.transformStyle = 'preserve-3d';
+
+	domElement.appendChild( cameraElement );
+
+	var isIE = /Trident/i.test( navigator.userAgent );
+
+	this.getSize = function () {
+
+		return {
+			width: _width,
+			height: _height
+		};
+
+	};
+
+	this.setSize = function ( width, height ) {
+
+		_width = width;
+		_height = height;
+		_widthHalf = _width / 2;
+		_heightHalf = _height / 2;
+
+		domElement.style.width = width + 'px';
+		domElement.style.height = height + 'px';
+
+		cameraElement.style.width = width + 'px';
+		cameraElement.style.height = height + 'px';
+
+	};
+
+	function epsilon( value ) {
+
+		return Math.abs( value ) < 1e-10 ? 0 : value;
+
+	}
+
+	function getCameraCSSMatrix( matrix ) {
+
+		var elements = matrix.elements;
+
+		return 'matrix3d(' +
+			epsilon( elements[ 0 ] ) + ',' +
+			epsilon( - elements[ 1 ] ) + ',' +
+			epsilon( elements[ 2 ] ) + ',' +
+			epsilon( elements[ 3 ] ) + ',' +
+			epsilon( elements[ 4 ] ) + ',' +
+			epsilon( - elements[ 5 ] ) + ',' +
+			epsilon( elements[ 6 ] ) + ',' +
+			epsilon( elements[ 7 ] ) + ',' +
+			epsilon( elements[ 8 ] ) + ',' +
+			epsilon( - elements[ 9 ] ) + ',' +
+			epsilon( elements[ 10 ] ) + ',' +
+			epsilon( elements[ 11 ] ) + ',' +
+			epsilon( elements[ 12 ] ) + ',' +
+			epsilon( - elements[ 13 ] ) + ',' +
+			epsilon( elements[ 14 ] ) + ',' +
+			epsilon( elements[ 15 ] ) +
+		')';
+
+	}
+
+	function getObjectCSSMatrix( matrix, cameraCSSMatrix ) {
+
+		var elements = matrix.elements;
+		var matrix3d = 'matrix3d(' +
+			epsilon( elements[ 0 ] ) + ',' +
+			epsilon( elements[ 1 ] ) + ',' +
+			epsilon( elements[ 2 ] ) + ',' +
+			epsilon( elements[ 3 ] ) + ',' +
+			epsilon( - elements[ 4 ] ) + ',' +
+			epsilon( - elements[ 5 ] ) + ',' +
+			epsilon( - elements[ 6 ] ) + ',' +
+			epsilon( - elements[ 7 ] ) + ',' +
+			epsilon( elements[ 8 ] ) + ',' +
+			epsilon( elements[ 9 ] ) + ',' +
+			epsilon( elements[ 10 ] ) + ',' +
+			epsilon( elements[ 11 ] ) + ',' +
+			epsilon( elements[ 12 ] ) + ',' +
+			epsilon( elements[ 13 ] ) + ',' +
+			epsilon( elements[ 14 ] ) + ',' +
+			epsilon( elements[ 15 ] ) +
+		')';
+
+		if ( isIE ) {
+
+			return 'translate(-50%,-50%)' +
+				'translate(' + _widthHalf + 'px,' + _heightHalf + 'px)' +
+				cameraCSSMatrix +
+				matrix3d;
+
+		}
+
+		return 'translate(-50%,-50%)' + matrix3d;
+
+	}
+
+	function renderObject( object, camera, cameraCSSMatrix ) {
+
+		if ( object instanceof CSS3DObject ) {
+
+			var style;
+
+			if ( object instanceof CSS3DSprite ) {
+
+				// http://swiftcoder.wordpress.com/2008/11/25/constructing-a-billboard-matrix/
+
+				matrix.copy( camera.matrixWorldInverse );
+				matrix.transpose();
+				matrix.copyPosition( object.matrixWorld );
+				matrix.scale( object.scale );
+
+				matrix.elements[ 3 ] = 0;
+				matrix.elements[ 7 ] = 0;
+				matrix.elements[ 11 ] = 0;
+				matrix.elements[ 15 ] = 1;
+
+				style = getObjectCSSMatrix( matrix, cameraCSSMatrix );
+
+			} else {
+
+				style = getObjectCSSMatrix( object.matrixWorld, cameraCSSMatrix );
+
+			}
+
+			var element = object.element;
+			var cachedObject = cache.objects.get( object );
+
+			if ( cachedObject === undefined || cachedObject.style !== style ) {
+
+				element.style.WebkitTransform = style;
+				element.style.transform = style;
+
+				var objectData = { style: style };
+
+				if ( isIE ) {
+
+					objectData.distanceToCameraSquared = getDistanceToSquared( camera, object );
+
+				}
+
+				cache.objects.set( object, objectData );
+
+			}
+
+			if ( element.parentNode !== cameraElement ) {
+
+				cameraElement.appendChild( element );
+
+			}
+
+		}
+
+		for ( var i = 0, l = object.children.length; i < l; i ++ ) {
+
+			renderObject( object.children[ i ], camera, cameraCSSMatrix );
+
+		}
+
+	}
+
+	var getDistanceToSquared = function () {
+
+		var a = new Vector3();
+		var b = new Vector3();
+
+		return function ( object1, object2 ) {
+
+			a.setFromMatrixPosition( object1.matrixWorld );
+			b.setFromMatrixPosition( object2.matrixWorld );
+
+			return a.distanceToSquared( b );
+
+		};
+
+	}();
+
+	function filterAndFlatten( scene ) {
+
+		var result = [];
+
+		scene.traverse( function ( object ) {
+
+			if ( object instanceof CSS3DObject ) result.push( object );
+
+		} );
+
+		return result;
+
+	}
+
+	function zOrder( scene ) {
+
+		var sorted = filterAndFlatten( scene ).sort( function ( a, b ) {
+
+			var distanceA = cache.objects.get( a ).distanceToCameraSquared;
+			var distanceB = cache.objects.get( b ).distanceToCameraSquared;
+
+			return distanceA - distanceB;
+
+		} );
+
+		var zMax = sorted.length;
+
+		for ( var i = 0, l = sorted.length; i < l; i ++ ) {
+
+			sorted[ i ].element.style.zIndex = zMax - i;
+
+		}
+
+	}
+
+	this.render = function ( scene, camera ) {
+
+		var fov = camera.projectionMatrix.elements[ 5 ] * _heightHalf;
+
+		if ( cache.camera.fov !== fov ) {
+
+			if ( camera.isPerspectiveCamera ) {
+
+				domElement.style.WebkitPerspective = fov + 'px';
+				domElement.style.perspective = fov + 'px';
+
+			}
+
+			cache.camera.fov = fov;
+
+		}
+
+		scene.updateMatrixWorld();
+
+		if ( camera.parent === null ) camera.updateMatrixWorld();
+
+		if ( camera.isOrthographicCamera ) {
+
+			var tx = - ( camera.right + camera.left ) / 2;
+			var ty = ( camera.top + camera.bottom ) / 2;
+
+		}
+
+		var cameraCSSMatrix = camera.isOrthographicCamera ?
+			'scale(' + fov + ')' + 'translate(' + epsilon( tx ) + 'px,' + epsilon( ty ) + 'px)' + getCameraCSSMatrix( camera.matrixWorldInverse ) :
+			'translateZ(' + fov + 'px)' + getCameraCSSMatrix( camera.matrixWorldInverse );
+
+		var style = cameraCSSMatrix +
+			'translate(' + _widthHalf + 'px,' + _heightHalf + 'px)';
+
+		if ( cache.camera.style !== style && ! isIE ) {
+
+			cameraElement.style.WebkitTransform = style;
+			cameraElement.style.transform = style;
+
+			cache.camera.style = style;
+
+		}
+
+		renderObject( scene, camera, cameraCSSMatrix );
+
+		if ( isIE ) {
+
+			// IE10 and 11 does not support 'preserve-3d'.
+			// Thus, z-order in 3D will not work.
+			// We have to calc z-order manually and set CSS z-index for IE.
+			// FYI: z-index can't handle object intersection
+			zOrder( scene );
+
+		}
+
+	};
+
+};
+
+export { CSS3DObject, CSS3DSprite, CSS3DRenderer };

+ 3 - 2
examples/jsm/utils/ShadowMapViewer.js

@@ -63,7 +63,7 @@ var ShadowMapViewer = function ( light ) {
 	//HUD for shadow map
 	var shader = UnpackDepthRGBAShader;
 
-	var uniforms = new UniformsUtils.clone( shader.uniforms );
+	var uniforms = UniformsUtils.clone( shader.uniforms );
 	var material = new ShaderMaterial( {
 		uniforms: uniforms,
 		vertexShader: shader.vertexShader,
@@ -109,7 +109,7 @@ var ShadowMapViewer = function ( light ) {
 	}
 
 
-	function resetPosition () {
+	function resetPosition() {
 
 		scope.position.set( scope.position.x, scope.position.y );
 
@@ -187,6 +187,7 @@ var ShadowMapViewer = function ( light ) {
 			 camera.updateProjectionMatrix();
 
 			 this.update();
+
 		}
 
 	};

BIN
examples/textures/memorial.png


+ 2 - 5
examples/webgl_gpgpu_water.html

@@ -633,7 +633,6 @@
 				var currentRenderTarget = gpuCompute.getCurrentRenderTarget( heightmapVariable );
 
 				readWaterLevelShader.uniforms[ "texture" ].value = currentRenderTarget.texture;
-				var gl = renderer.context;
 
 				for ( var i = 0; i < NUM_SPHERES; i ++ ) {
 
@@ -646,11 +645,9 @@
 						var v = 1 - ( 0.5 * sphere.position.z / BOUNDS_HALF + 0.5 );
 						readWaterLevelShader.uniforms[ "point1" ].value.set( u, v );
 						gpuCompute.doRenderTarget( readWaterLevelShader, readWaterLevelRenderTarget );
-						var previousRenderTarget = renderer.getRenderTarget();
-						renderer.setRenderTarget( readWaterLevelRenderTarget );
-						gl.readPixels( 0, 0, 4, 1, gl.RGBA, gl.UNSIGNED_BYTE, readWaterLevelImage );
+
+						renderer.readRenderTargetPixels( readWaterLevelRenderTarget, 0, 0, 4, 1, readWaterLevelImage );
 						var pixels = new Float32Array( readWaterLevelImage.buffer );
-						renderer.setRenderTarget( previousRenderTarget );
 
 						// Get orientation
 						waterNormal.set( pixels[ 1 ], 0, - pixels[ 2 ] );

+ 46 - 1
examples/webgl_loader_ldraw.html

@@ -109,7 +109,10 @@
 				guiData = {
 					modelFileName: modelFileList[ 'Car' ],
 					envMapActivated: false,
-					separateObjects: false
+					separateObjects: false,
+					displayLines: true,
+					conditionalLines: false,
+					smoothNormals: true
 				};
 
 				gui = new dat.GUI();
@@ -134,6 +137,23 @@
 
 				} );
 
+				gui.add( guiData, 'smoothNormals' ).name( 'Smooth Normals' ).onChange( function ( value ) {
+
+					reloadObject( false );
+
+				} );
+
+				gui.add( guiData, 'displayLines' ).name( 'Display Lines' ).onChange( function ( value ) {
+
+					updateLineSegments();
+
+				} );
+
+				gui.add( guiData, 'conditionalLines' ).name( 'Conditional Lines' ).onChange( function ( value ) {
+
+					updateLineSegments();
+
+				} );
 				window.addEventListener( 'resize', onWindowResize, false );
 
 				progressBarDiv = document.createElement( 'div' );
@@ -153,6 +173,28 @@
 
 			}
 
+			function updateLineSegments() {
+
+				model.traverse( c => {
+
+					if ( c.isLineSegments ) {
+
+						if ( c.isConditionalLine ) {
+
+							c.visible = guiData.conditionalLines;
+
+						} else {
+
+							c.visible = guiData.displayLines;
+
+						}
+
+					}
+
+				} );
+
+			}
+
 			function reloadObject( resetCamera ) {
 
 				if ( model ) {
@@ -168,6 +210,7 @@
 
 				var lDrawLoader = new THREE.LDrawLoader();
 				lDrawLoader.separateObjects = guiData.separateObjects;
+				lDrawLoader.smoothNormals = guiData.smoothNormals;
 				lDrawLoader
 					.setPath( ldrawPath )
 					.load( guiData.modelFileName, function ( group2 ) {
@@ -218,6 +261,8 @@
 
 						}
 
+						updateLineSegments();
+
 						// Adjust camera and light
 
 						var bbox = new THREE.Box3().setFromObject( model );

+ 3 - 3
examples/webgl_loader_svg.html

@@ -87,7 +87,7 @@
 					drawStrokes: true,
 					fillShapesWireframe: false,
 					strokesWireframe: false
-				}
+				};
 
 				loadSVG( guiData.currentURL );
 
@@ -166,7 +166,7 @@
 						var path = paths[ i ];
 
 						var fillColor = path.userData.style.fill;
-						if ( guiData.drawFillShapes && fillColor !== undefined && fillColor !== 'none') {
+						if ( guiData.drawFillShapes && fillColor !== undefined && fillColor !== 'none' ) {
 
 							var material = new THREE.MeshBasicMaterial( {
 								color: new THREE.Color().setStyle( fillColor ),
@@ -207,7 +207,7 @@
 
 							for ( var j = 0, jl = path.subPaths.length; j < jl; j ++ ) {
 
-								subPath = path.subPaths[ j ];
+								var subPath = path.subPaths[ j ];
 
 								var geometry = THREE.SVGLoader.pointsToStroke( subPath.getPoints(), path.userData.style );
 

+ 140 - 0
examples/webgl_loader_texture_rgbm.html

@@ -0,0 +1,140 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<title>three.js webgl - materials - RGBM texture loader</title>
+		<meta charset="utf-8">
+		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
+		<style>
+			body {
+				color: #fff;
+				font-family:Monospace;
+				font-size:13px;
+				text-align:center;
+				font-weight: bold;
+
+				background-color: #000;
+				margin: 0px;
+				overflow: hidden;
+			}
+
+			#info {
+				color:#fff;
+				position: absolute;
+				top: 0px; width: 100%;
+				padding: 5px;
+
+			}
+
+			a { color: red; }
+
+		</style>
+	</head>
+	<body>
+
+		<div id="info">
+			<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - webgl RGBM texture loader example
+		</div>
+
+		<script src="../build/three.js"></script>
+
+		<script src="js/libs/dat.gui.min.js"></script>
+
+		<script src="js/WebGL.js"></script>
+
+		<script>
+
+			if ( WEBGL.isWebGLAvailable() === false ) {
+
+				document.body.appendChild( WEBGL.getWebGLErrorMessage() );
+
+			}
+
+			var params = {
+				exposure: 2.0
+			};
+
+			var renderer, scene, camera;
+
+			init();
+
+			function init() {
+
+				renderer = new THREE.WebGLRenderer();
+				renderer.setPixelRatio( window.devicePixelRatio );
+				renderer.setSize( window.innerWidth, window.innerHeight );
+				document.body.appendChild( renderer.domElement );
+
+				renderer.toneMapping = THREE.ReinhardToneMapping;
+				renderer.toneMappingExposure = params.exposure;
+
+				renderer.gammaOutput = true;
+
+				scene = new THREE.Scene();
+
+				var aspect = window.innerWidth / window.innerHeight;
+
+				camera = new THREE.OrthographicCamera( - aspect, aspect, 1, - 1, 0, 1 );
+
+				new THREE.TextureLoader().load( 'textures/memorial.png', function ( texture ) {
+
+					texture.encoding = THREE.RGBM16Encoding;
+
+					texture.minFilter = THREE.LinearFilter;
+					texture.magFilter = THREE.LinearFilter;
+					texture.flipY = true;
+
+					var material = new THREE.MeshBasicMaterial( { map: texture } );
+
+					var quad = new THREE.PlaneBufferGeometry( 1.5 * texture.width / texture.height, 1.5 );
+
+					var mesh = new THREE.Mesh( quad, material );
+
+					scene.add( mesh );
+
+					render();
+
+				} );
+
+				//
+
+				var gui = new dat.GUI();
+
+				gui.add( params, 'exposure', 0, 4, 0.01 ).onChange( render );
+				gui.open();
+
+				//
+
+				window.addEventListener( 'resize', onWindowResize, false );
+
+			}
+
+			function onWindowResize() {
+
+				var aspect = window.innerWidth / window.innerHeight;
+
+				var frustumHeight = camera.top - camera.bottom;
+
+				camera.left = - frustumHeight * aspect / 2;
+				camera.right = frustumHeight * aspect / 2;
+
+				camera.updateProjectionMatrix();
+
+				renderer.setSize( window.innerWidth, window.innerHeight );
+
+				render();
+
+			}
+
+			//
+
+			function render() {
+
+				renderer.toneMappingExposure = params.exposure;
+
+				renderer.render( scene, camera );
+
+			}
+
+		</script>
+	</body>
+</html>

+ 1 - 1
files/main.css

@@ -194,7 +194,7 @@ h1 a {
 	}
 	#panel #content {
 		flex: 1;
-		overflow: scroll;
+		overflow-y: scroll;
 		padding: 0 var(--panel-padding) 24px var(--panel-padding);
 	}
 

+ 1 - 1
src/core/Geometry.js

@@ -228,7 +228,7 @@ Geometry.prototype = Object.assign( Object.create( EventDispatcher.prototype ),
 
 		if ( uvs2 !== undefined ) this.faceVertexUvs[ 1 ] = [];
 
-		for ( var i = 0, j = 0; i < positions.length; i += 3, j += 2 ) {
+		for ( var i = 0; i < positions.length; i += 3 ) {
 
 			scope.vertices.push( new Vector3().fromArray( positions, i ) );
 

+ 12 - 0
src/core/InstancedBufferGeometry.js

@@ -33,6 +33,18 @@ InstancedBufferGeometry.prototype = Object.assign( Object.create( BufferGeometry
 
 		return new this.constructor().copy( this );
 
+	},
+
+	toJSON: function () {
+
+		var data = BufferGeometry.prototype.toJSON.call( this );
+
+		data.maxInstancedCount = this.maxInstancedCount;
+
+		data.isInstancedBufferGeometry = true;
+
+		return data;
+
 	}
 
 } );

+ 5 - 0
src/extras/core/Interpolations.d.ts

@@ -0,0 +1,5 @@
+export namespace Interpolations {
+	export function CatmullRom(t: number, p0: number, p1: number, p2: number, p3: number): number;
+	export function QuadraticBezier(t: number, p0: number, p1: number, p2: number): number;
+	export function CubicBezier(t: number, p0: number, p1: number, p2: number, p3: number): number;
+}

+ 12 - 2
src/geometries/SphereGeometry.js

@@ -64,7 +64,7 @@ function SphereBufferGeometry( radius, widthSegments, heightSegments, phiStart,
 	thetaStart = thetaStart !== undefined ? thetaStart : 0;
 	thetaLength = thetaLength !== undefined ? thetaLength : Math.PI;
 
-	var thetaEnd = thetaStart + thetaLength;
+	var thetaEnd = Math.min( thetaStart + thetaLength, Math.PI );
 
 	var ix, iy;
 
@@ -91,7 +91,17 @@ function SphereBufferGeometry( radius, widthSegments, heightSegments, phiStart,
 
 		// special case for the poles
 
-		var uOffset = ( iy == 0 ) ? 0.5 / widthSegments : ( ( iy == heightSegments ) ? - 0.5 / widthSegments : 0 );
+		var uOffset = 0;
+
+		if ( iy == 0 && thetaStart == 0 ) {
+
+			uOffset = 0.5 / widthSegments;
+
+		} else if ( iy == heightSegments && thetaEnd == Math.PI ) {
+
+			uOffset = - 0.5 / widthSegments;
+
+		}
 
 		for ( ix = 0; ix <= widthSegments; ix ++ ) {
 

+ 5 - 3
src/loaders/BufferGeometryLoader.js

@@ -4,6 +4,8 @@ import { BufferAttribute } from '../core/BufferAttribute.js';
 import { BufferGeometry } from '../core/BufferGeometry.js';
 import { FileLoader } from './FileLoader.js';
 import { DefaultLoadingManager } from './LoadingManager.js';
+import { InstancedBufferGeometry } from '../core/InstancedBufferGeometry.js';
+import { InstancedBufferAttribute } from '../core/InstancedBufferAttribute.js';
 
 /**
  * @author mrdoob / http://mrdoob.com/
@@ -33,7 +35,7 @@ Object.assign( BufferGeometryLoader.prototype, {
 
 	parse: function ( json ) {
 
-		var geometry = new BufferGeometry();
+		var geometry = json.isInstancedBufferGeometry ? new InstancedBufferGeometry() : new BufferGeometry();
 
 		var index = json.data.index;
 
@@ -50,8 +52,8 @@ Object.assign( BufferGeometryLoader.prototype, {
 
 			var attribute = attributes[ key ];
 			var typedArray = new TYPED_ARRAYS[ attribute.type ]( attribute.array );
-
-			var bufferAttribute = new BufferAttribute( typedArray, attribute.itemSize, attribute.normalized );
+			var bufferAttributeConstr = attribute.isInstancedBufferAttribute ? InstancedBufferAttribute : BufferAttribute;
+			var bufferAttribute = new bufferAttributeConstr( typedArray, attribute.itemSize, attribute.normalized );
 			if ( attribute.name !== undefined ) bufferAttribute.name = attribute.name;
 			geometry.addAttribute( key, bufferAttribute );
 

+ 1 - 0
src/loaders/ObjectLoader.js

@@ -430,6 +430,7 @@ Object.assign( ObjectLoader.prototype, {
 						break;
 
 					case 'BufferGeometry':
+					case 'InstancedBufferGeometry':
 
 						geometry = bufferGeometryLoader.parse( data );
 

+ 1 - 1
src/renderers/WebGLRenderer.js

@@ -308,7 +308,7 @@ function WebGLRenderer( parameters ) {
 
 	// vr
 
-	var vr = ( typeof navigator !== 'undefined' && 'xr' in navigator ) ? new WebXRManager( _this ) : new WebVRManager( _this );
+	var vr = ( typeof navigator !== 'undefined' && 'xr' in navigator && 'requestDevice' in navigator.xr ) ? new WebXRManager( _this ) : new WebVRManager( _this );
 
 	this.vr = vr;
 

+ 1 - 1
src/renderers/webgl/WebGLAnimation.d.ts

@@ -5,5 +5,5 @@ export class WebGLAnimation {
 
 	setAnimationLoop(callback: Function): void;
 
-	setContext(value: CanvasRenderingContext2D | WebGLRenderingContext): void;
+	setContext(value: WebGLRenderingContext | WebGL2RenderingContext): void;
 }

+ 2 - 2
src/renderers/webgl/WebGLAttributes.d.ts

@@ -1,9 +1,9 @@
 export class WebGLAttributes {
-	constructor(gl: CanvasRenderingContext2D | WebGLRenderingContext);
+	constructor(gl: WebGLRenderingContext | WebGL2RenderingContext);
 
 	get(attribute: any): any;
 
 	remove(attribute: any): void;
 
-	update(attribute: any, bufferType: Array): void;
+	update(attribute: any, bufferType: Array<any>): void;
 }

+ 17 - 0
src/renderers/webgl/WebGLBackground.d.ts

@@ -0,0 +1,17 @@
+
+import { Color } from "../../math/Color.js";
+import { WebGLRenderer } from "../WebGLRenderer.js"
+import { WebGLState } from "./WebGLState.js"
+import { WebGLObjects } from "./WebGLObjects.js"
+import { WebGLRenderLists } from "./WebGLRenderLists.js"
+import { Scene } from "../../scenes/Scene.js"
+
+export class WebGLBackground {
+	constructor(renderer: WebGLRenderer, state: WebGLState, objects: WebGLObjects, premultipliedAlpha: any);
+
+	getClearColor(): void;
+	setClearColor(color: Color, alpha: any): void;
+	getClearAlpha(): void;
+	setClearAlpha(alpha: any): void;
+	render(renderList: WebGLRenderLists, scene: Scene, camera: any, forceClear: any): void;
+}

+ 5 - 0
src/renderers/webgl/WebGLUtils.d.ts

@@ -0,0 +1,5 @@
+export class WebGLUtils {
+	constructor(gl: WebGLRenderingContext | WebGL2RenderingContext, extensions: any, capabilities: any);
+
+	convert(p: any): void;
+}

+ 26 - 10
utils/modularize.js

@@ -8,10 +8,17 @@ var srcFolder = __dirname + '/../examples/js/';
 var dstFolder = __dirname + '/../examples/jsm/';
 
 var files = [
+	{ path: 'controls/DragControls.js', ignoreList: [] },
+	{ path: 'controls/DeviceOrientationControls.js', ignoreList: [] },
+	{ path: 'controls/EditorControls.js', ignoreList: [] },
+	{ path: 'controls/FirstPersonControls.js', ignoreList: [] },
+	{ path: 'controls/FlyControls.js', ignoreList: [] },
 	{ path: 'controls/OrbitControls.js', ignoreList: [] },
 	{ path: 'controls/MapControls.js', ignoreList: [] },
+	{ path: 'controls/OrthographicTrackballControls.js', ignoreList: [] },
+	{ path: 'controls/PointerLockControls.js', ignoreList: [] },
 	{ path: 'controls/TrackballControls.js', ignoreList: [] },
-	// { path: 'controls/TransformControls.js', ignoreList: [] },
+	{ path: 'controls/TransformControls.js', ignoreList: [] },
 
 	{ path: 'exporters/GLTFExporter.js', ignoreList: [ 'AnimationClip', 'Camera', 'Geometry', 'Material', 'Mesh', 'Object3D', 'RGBFormat', 'Scenes', 'ShaderMaterial', 'VertexColors' ] },
 	{ path: 'exporters/MMDExporter.js', ignoreList: [] },
@@ -20,14 +27,23 @@ var files = [
 	{ path: 'exporters/STLExporter.js', ignoreList: [] },
 	{ path: 'exporters/TypedGeometryExporter.js', ignoreList: [] },
 
+	{ path: 'loaders/BVHLoader.js', ignoreList: [ 'Bones' ] },
+	{ path: 'loaders/PCDLoader.js', ignoreList: [] },
 	{ path: 'loaders/GLTFLoader.js', ignoreList: [ 'NoSide', 'Matrix2', 'DDSLoader' ] },
 	{ path: 'loaders/OBJLoader.js', ignoreList: [] },
-	{ path: 'loaders/MTLLoader.js', ignoreList: [] },
-	{ path: 'loaders/STLLoader.js', ignoreList: [] },
+	{ path: 'loaders/MTLLoader.js', ignoreList: [ 'BackSide', 'DoubleSide', 'ClampToEdgeWrapping', 'MirroredRepeatWrapping' ] },
+	{ path: 'loaders/PLYLoader.js', ignoreList: [ 'Mesh' ] },
+	{ path: 'loaders/STLLoader.js', ignoreList: [ 'Mesh', 'MeshPhongMaterial', 'VertexColors' ] },
+	{ path: 'loaders/SVGLoader.js', ignoreList: [] },
+	{ path: 'loaders/TGALoader.js', ignoreList: [] },
+	{ path: 'loaders/VRMLLoader.js', ignoreList: [] },
 
 	{ path: 'pmrem/PMREMCubeUVPacker.js', ignoreList: [] },
 	{ path: 'pmrem/PMREMGenerator.js', ignoreList: [] },
 
+	{ path: 'renderers/CSS2DRenderer.js', ignoreList: [] },
+	{ path: 'renderers/CSS3DRenderer.js', ignoreList: [] },
+
 	{ path: 'utils/BufferGeometryUtils.js', ignoreList: [] },
 	{ path: 'utils/GeometryUtils.js', ignoreList: [] },
 	{ path: 'utils/MathUtils.js', ignoreList: [] },
@@ -51,7 +67,7 @@ function convert( path, ignoreList ) {
 
 	var contents = fs.readFileSync( srcFolder + path, 'utf8' );
 
-	var className = '';
+	var classNames = [];
 	var dependencies = {};
 
 	// imports
@@ -66,9 +82,9 @@ function convert( path, ignoreList ) {
 
 	contents = contents.replace( /THREE\.([a-zA-Z0-9]+) = /g, function ( match, p1 ) {
 
-		className = p1;
+		classNames.push( p1 );
 
-		console.log( className );
+		console.log( p1 );
 
 		return `var ${p1} = `;
 
@@ -77,7 +93,7 @@ function convert( path, ignoreList ) {
 	contents = contents.replace( /(\'?)THREE\.([a-zA-Z0-9]+)(\.{0,1})/g, function ( match, p1, p2, p3 ) {
 
 		if ( p1 === '\'' ) return match; // Inside a string
-		if ( p2 === className ) return `${p2}${p3}`;
+		if ( classNames.includes( p2 ) ) return `${p2}${p3}`;
 
 		if ( p1 === 'Math' ) {
 
@@ -109,7 +125,7 @@ function convert( path, ignoreList ) {
 
 		if ( ignoreList.includes( p2 ) ) return match;
 		if ( p1 === '\'' ) return match; // Inside a string
-		if ( p2 === className ) return p2;
+		if ( classNames.includes( p2 ) ) return p2;
 
 		if ( p2 === 'Math' || p2 === '_Math' ) {
 
@@ -130,13 +146,13 @@ function convert( path, ignoreList ) {
 	//
 
 	var keys = Object.keys( dependencies )
-		.filter( value => value !== className )
+		.filter( value => ! classNames.includes( value ) )
 		.map( value => value === '_Math' ? 'Math as _Math' : value )
 		.map( value => '\n\t' + value )
 		.sort()
 		.toString();
 	var imports = `import {${keys}\n} from "../../../build/three.module.js";`;
-	var exports = `export { ${className} };\n`;
+	var exports = `export { ${classNames.join( ", " )} };\n`;
 
 	var output = contents.replace( '_IMPORTS_', keys ? imports : '' ) + '\n' + exports;