Browse Source

Merge branch 'dev' into feature/gltf-dedupe-geometry

mattdesl 7 years ago
parent
commit
1b1a09279c
57 changed files with 3661 additions and 49579 deletions
  1. 568 449
      build/three.js
  2. 323 316
      build/three.min.js
  3. 568 449
      build/three.module.js
  4. 31 0
      docs/api/extras/Earcut.html
  5. 2 36
      docs/api/extras/ShapeUtils.html
  6. 1 2
      docs/api/geometries/TextBufferGeometry.html
  7. 1 1
      docs/api/loaders/Cache.html
  8. 1 3
      docs/api/loaders/FontLoader.html
  9. 1 1
      docs/api/math/Vector3.html
  10. 27 0
      docs/api/textures/DataTexture.html
  11. 1 0
      docs/examples/exporters/GLTFExporter.html
  12. 12 11
      docs/examples/loaders/LoaderSupport.html
  13. 1 0
      docs/list.js
  14. 1 39
      editor/js/libs/tern-threejs/threejs.js
  15. 4 2
      examples/canvas_geometry_panorama.html
  16. 4 2
      examples/canvas_geometry_panorama_fisheye.html
  17. 0 2
      examples/files.js
  18. 229 32
      examples/js/exporters/GLTFExporter.js
  19. 0 654
      examples/js/libs/earcut.js
  20. 0 19
      examples/js/libs/pnltri.min.js
  21. 7 1
      examples/js/loaders/ColladaLoader.js
  22. 503 586
      examples/js/loaders/FBXLoader.js
  23. 78 16
      examples/js/loaders/GLTFLoader.js
  24. 289 228
      examples/js/loaders/LoaderSupport.js
  25. 19 9
      examples/js/loaders/OBJLoader2.js
  26. 13 1
      examples/js/loaders/STLLoader.js
  27. 20 23
      examples/js/loaders/TDSLoader.js
  28. 9 4
      examples/js/loaders/XLoader.js
  29. 0 136
      examples/js/utils/ImageUtils.js
  30. 0 2
      examples/js/vr/ViveController.js
  31. 0 617
      examples/webgl_geometry_text_earcut.html
  32. 0 589
      examples/webgl_geometry_text_pnltri.html
  33. 3 2
      examples/webgl_interactive_raycasting_points.html
  34. 19 10
      examples/webgl_loader_obj2_meshspray.html
  35. 4 1
      examples/webgl_loader_obj2_options.html
  36. 20 4
      examples/webgl_loader_obj2_run_director.html
  37. 1 1
      examples/webgl_materials_cubemap_dynamic2.html
  38. 1 1
      examples/webgl_panorama_cube.html
  39. 1 2
      examples/webvr_panorama.html
  40. 1 1
      examples/webvr_rollercoaster.html
  41. 0 2
      examples/webvr_vive.html
  42. 0 2
      examples/webvr_vive_dragging.html
  43. 0 2
      examples/webvr_vive_paint.html
  44. 0 2
      examples/webvr_vive_sculpt.html
  45. 25 0
      src/Three.Legacy.js
  46. 810 0
      src/extras/Earcut.js
  47. 27 625
      src/extras/ShapeUtils.js
  48. 1 1
      src/geometries/ExtrudeGeometry.js
  49. 18 10
      src/math/Math.js
  50. 3 1
      src/objects/Group.js
  51. 13 13
      src/renderers/webgl/WebGLProgram.js
  52. 0 29
      src/renderers/webvr/WebVRManager.js
  53. 0 6
      test/unit/src/renderers/webvr/WebVRManager.tests.js
  54. 0 9435
      test/unit/three.editor.unit.js
  55. 0 15
      test/unit/three.example.unit.js
  56. 0 35183
      test/unit/three.source.unit.js
  57. 1 1
      utils/build/externs.js

File diff suppressed because it is too large
+ 568 - 449
build/three.js


File diff suppressed because it is too large
+ 323 - 316
build/three.min.js


File diff suppressed because it is too large
+ 568 - 449
build/three.module.js


+ 31 - 0
docs/api/extras/Earcut.html

@@ -0,0 +1,31 @@
+<!DOCTYPE html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8" />
+		<base href="../../" />
+		<script src="list.js"></script>
+		<script src="page.js"></script>
+		<link type="text/css" rel="stylesheet" href="page.css" />
+	</head>
+	<body>
+		<h1>[name]</h1>
+
+		<div class="desc">
+		An implementation of the earcut polygon triangulation algorithm. The code is a port of [link:https://github.com/mapbox/earcut mapbox/earcut].
+		</div>
+
+		<h2>Methods</h2>
+
+		<h3>[method:Array triangulate]( data, holeIndices, dim )</h3>
+		<div>
+		data -- A flat array of vertice coordinates.<br /><br />
+		holeIndices -- An array of hole indices if any.<br /><br />
+		dim -- The number of coordinates per vertice in the input array.<br /><br />
+
+		</div>
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>

+ 2 - 36
docs/api/extras/ShapeUtils.html

@@ -28,30 +28,6 @@
 
 		</div>
 
-		<h3>[method:Number b2]( t, p0, p1, p2 )</h3>
-		<div>
-		t -- number<br />
-		p0, p1, p2 -- x, y, z or w components of a quadratic bezier curve.<br /><br />
-
-		Note that this is a linear function so it is neccessary to calculate separately for
-		x, y (and z for 3D curves) components of a curve.<br /><br />
-
-		Used internally by [page:QuadraticBezierCurve QuadraticBezierCurve],
-		[page:QuadraticBezierCurve3 QuadraticBezierCurve3] and [page:Font Font].
-		</div>
-
-		<h3>[method:Number b3]( t, p0, p1, p2, p3 )</h3>
-		<div>
-		t -- number. <br />
-		p0, p1, p2, p3 -- x, y or z components of a cubic bezier curve..<br /><br />
-
-		Note that this is a linear function so it is neccessary to calculate separately for
-		x, y (and z for 3D curves) components of a curve.<br /><br />
-
-		Used internally by [page:CubicBezierCurve CubicBezierCurve],
-		[page:CubicBezierCurve3 CubicBezierCurve3] and [page:Font Font].
-		</div>
-
 		<h3>[method:Boolean isClockwise]( pts )</h3>
 		<div>
 		pts -- points defining a 2D polygon<br /><br />
@@ -60,16 +36,7 @@
 		x, y  components of a polygon.<br /><br />
 
 		Used internally by [page:Path Path],
-		[page:ExtrudeGeometry ExtrudeGeometry] and [page:ShapeBufferGeometry ShapeBufferGeometry].
-		</div>
-
-		<h3>[method:null triangulate]( contour, indices )</h3>
-		<div>
-		contour --  2D polygon.<br />
-		indices -- <br /><br />
-
-		Used internally by [page:ExtrudeGeometry ExtrudeGeometry]
-		and [page:ShapeBufferGeometry ShapeBufferGeometry] to calculate faces.
+		[page:ExtrudeGeometry ExtrudeGeometry] and [page:ShapeGeometry ShapeGeometry].
 		</div>
 
 		<h3>[method:null triangulateShape]( contour, holes )</h3>
@@ -77,8 +44,7 @@
 		contour -- 2D polygon.<br />
 		holes -- array of holes<br /><br />
 
-		Used internally by [page:ExtrudeGeometry ExtrudeGeometry]
-		and [page:ShapeBufferGeometry ShapeBufferGeometry] to calculate faces in shapes with holes.
+		Used internally by [page:ExtrudeGeometry ExtrudeGeometry] and [page:ShapeGeometry ShapeGeometry] to calculate faces in shapes with holes.
 		</div>
 
 		<h2>Source</h2>

+ 1 - 2
docs/api/geometries/TextBufferGeometry.html

@@ -39,8 +39,7 @@
 		<h2>Examples</h2>
 
 		<div>
-		[example:webgl_geometry_text geometry / text ]<br/>
-		[example:webgl_geometry_text_pnltri geometry / text2 ]
+		[example:webgl_geometry_text geometry / text ]
 		</div>
 
 		<code>

+ 1 - 1
docs/api/loaders/Cache.html

@@ -17,7 +17,7 @@
 		<h2>Examples</h2>
 
 		<div>
-			[example:webgl_geometry_text_earcut WebGL / geometry / text / earcut]<br />
+			[example:webgl_geometry_text WebGL / geometry / text ]<br />
 			[example:webgl_interactive_instances_gpu WebGL / interactive / instances / gpu]<br />
 			[example:webgl_loader_ttf WebGL / loader / ttf]
 		</div>

+ 1 - 3
docs/api/loaders/FontLoader.html

@@ -22,9 +22,7 @@
 
 		<div>
 		[example:webgl_geometry_text_shapes geometry / text / shapes ]<br/>
-		[example:webgl_geometry_text geometry / text ]<br />
-		[example:webgl_geometry_text_earcut geometry / text / earcut]<br />
-		[example:webgl_geometry_text_pnltri geometry / text / pnltri]<br />
+		[example:webgl_geometry_text geometry / text ]
 		</div>
 
 		<code>

+ 1 - 1
docs/api/math/Vector3.html

@@ -301,7 +301,7 @@ var d = a.distanceTo( b );
 		<div>Multiplies this vector by scalar [page:Float s].</div>
 
 		<h3>[method:Vector3 multiplyVectors]( [page:Vector3 a], [page:Vector3 b] )</h3>
-		<div>Sets this vector equal to [page:Vector3 a] x [page:Vector3 b].</div>
+		<div>Sets this vector equal to [page:Vector3 a] * [page:Vector3 b], component-wise.</div>
 
 		<h3>[method:Vector3 project]( [page:Camera camera] )</h3>
 		<div>

+ 27 - 0
docs/api/textures/DataTexture.html

@@ -32,6 +32,33 @@
 			In order to use the types THREE.FloatType and THREE.HalfFloatType, the WebGL implementation must support the respective extensions OES_texture_float and OES_texture_half_float. In order to use THREE.LinearFilter for component-wise, bilinear interpolation of the texels based on these types, the WebGL extensions OES_texture_float_linear or OES_texture_half_float_linear must also be present.
 		</div>
 
+		<h2>Example</h2>
+
+		<code>
+		// create a buffer with color data
+
+		var size = width * height;
+		var data = new Uint8Array( 3 * size );
+
+		var r = Math.floor( color.r * 255 );
+		var g = Math.floor( color.g * 255 );
+		var b = Math.floor( color.b * 255 );
+
+		for ( var i = 0; i < size; i ++ ) {
+
+			var stride = i * 3;
+
+			data[ stride ] = r;
+			data[ stride + 1 ] = g;
+			data[ stride + 2 ] = b;
+
+		}
+
+		// used the buffer to create a [name]
+
+		var texture = new THREE.DataTexture( data, width, height, THREE.RGBFormat );
+		</code>
+
 		<h2>Properties</h2>
 
 		<h3>[property:Image image]</h3>

+ 1 - 0
docs/examples/exporters/GLTFExporter.html

@@ -80,6 +80,7 @@ exporter.parse( [ scene1, object1, object2, scene2 ], ...)
 			<li>truncateDrawRange - bool. Export just the attributes within the drawRange, if defined, instead of exporting the whole array. Default is true.</li>
 			<li>binary - bool. Export in binary (.glb) format, returning an ArrayBuffer. Default is false.</li>
 			<li>embedImages - bool. Export with images embedded into the glTF asset. Default is true.</li>
+			<li>animations - Array<[page:AnimationClip AnimationClip]>. List of animations to be included in the export.
 		</ul>
 		</div>
 		<div>

+ 12 - 11
docs/examples/loaders/LoaderSupport.html

@@ -156,10 +156,9 @@
 
 		<h2>Methods</h2>
 
-		<h3>[method:null validate] ( [page:Function functionCodeBuilder], [page:Boolean forceWorkerReload], Array of [page:String libLocations], [page:String libPath], [page:LoaderSupport.WorkerRunnerRefImpl runnerImpl] )</h3>
+		<h3>[method:null validate] ( [page:Function functionCodeBuilder], Array of [page:String libLocations], [page:String libPath], [page:LoaderSupport.WorkerRunnerRefImpl runnerImpl] )</h3>
 		<div>
 			[page:Function functionCodeBuilder] - Function that is invoked with funcBuildObject and funcBuildSingelton that allows stringification of objects and singletons.<br>
-			[page:Boolean forceWorkerReload] - Force re-build of the worker code.<br>
 			Array of [page:String libLocations] - URL of libraries that shall be added to worker code relative to libPath.<br>
 			[page:String libPath] - Base path used for loading libraries.<br>
 			[page:LoaderSupport.WorkerRunnerRefImpl runnerImpl] - The default worker parser wrapper implementation (communication and execution). An extended class could be passed here.
@@ -178,12 +177,6 @@
 		</div>
 
 
-		<h3>[method:null terminateWorker] ()</h3>
-		<div>
-			Terminate the worker and the code.
-		</div>
-
-
 		<h3>[method:null setCallbacks] ( [page:Function builder], [page:Function onLoad] )</h3>
 		<div>
 			[page:Function builder] - The builder function. Default is [page:LoaderSupport.Builder].<br>
@@ -251,7 +244,7 @@
 			- prepareWorkers<br>
 			- enqueueForRun<br>
 			- processQueue<br>
-			- deregister
+			- tearDown
 		</div>
 
 
@@ -283,7 +276,10 @@
 		</div>
 
 
-		<h3>[method:null deregister]()</h3>
+		<h3>[method:null tearDown]( [page:Function callbackOnFinishedProcessing] )</h3>
+		<div>
+			[page:Function callbackOnFinishedProcessing] - Function called once all workers finished processing.
+		</div>
 		<div>
 			Terminate all workers.
 		</div>
@@ -300,6 +296,11 @@
 			Returns the maximum number of workers.
 		</div>
 
+		<h3>[method:Boolean isRunning]()</h3>
+		<div>
+			Returns if any workers are running.
+		</div>
+
 
 		<h3>[method:null setCrossOrigin]( [page:String crossOrigin] )</h3>
 		<div>
@@ -543,7 +544,7 @@
 
 		<h2>Methods</h2>
 
-		<h3>[method:null isValid]( [page:Object input] )</h3>
+		<h3>[method:Boolean isValid]( [page:Object input] )</h3>
 		<div>
 			[page:Object input] - Can be anything
 		</div>

+ 1 - 0
docs/list.js

@@ -103,6 +103,7 @@ var list = {
 		},
 
 		"Extras": {
+			"Earcut": "api/extras/Earcut",
 			"SceneUtils": "api/extras/SceneUtils",
 			"ShapeUtils": "api/extras/ShapeUtils"
 		},

+ 1 - 39
editor/js/libs/tern-threejs/threejs.js

@@ -805,44 +805,6 @@
       "prototype": {},
       "!doc": "Contains handy functions geometry manipulations."
     },
-    "ImageUtils": {
-      "!url": "http://threejs.org/docs/#Reference/extras/ImageUtils",
-      "prototype": {
-        "crossOrigin": {
-          "!type": "string",
-          "!doc": "The crossOrigin string to implement CORS for loading the image from a different domain that allows CORS."
-        },
-        "generateDataTexture": {
-          "!type": "fn(width: number, height: number, color: number) -> +THREE.DataTexture",
-          "!doc": "Generates a texture of a single color. It is a DataTexture with format, RGBFormat."
-        },
-        "parseDDS": {
-          "!type": "fn(buffer: string, loadMipmaps: boolean) -> +THREE.CompressedTexture",
-          "!doc": "Parses a DDS Image from the string into a CompressedTexture."
-        },
-        "loadCompressedTexture": {
-          "!type": "fn(url: todo, mapping: todo, onLoad: todo, onError: todo) -> todo",
-          "!doc": "todo"
-        },
-        "loadTexture": {
-          "!type": "fn(url: string, mapping: UVMapping, onLoad: function, onError: function) -> todo",
-          "!doc": "todo"
-        },
-        "getNormalMap": {
-          "!type": "fn(image: todo, depth: todo) -> todo",
-          "!doc": "todo"
-        },
-        "loadCompressedTextureCube": {
-          "!type": "fn(array: todo, mapping: todo, onLoad: todo, onError: todo) -> todo",
-          "!doc": "todo"
-        },
-        "loadTextureCube": {
-          "!type": "fn(array: todo, mapping: todo, onLoad: todo, onError: todo) -> todo",
-          "!doc": "todo"
-        }
-      },
-      "!doc": "A Helper class to ease the loading of images of different types."
-    },
     "SceneUtils": {
       "!url": "http://threejs.org/docs/#Reference/extras/SceneUtils",
       "prototype": {
@@ -5137,7 +5099,7 @@
         },
         "image": {
           "!type": "Image",
-          "!doc": "An Image object, typically created using the ImageUtils or [page:ImageLoader ImageLoader] classes. The Image object can include an image (e.g., PNG, JPG, GIF, DDS), video (e.g., MP4, OGG/OGV), or set of six images for a cube map. To use video as a texture you need to have a playing HTML5 video element as a source for your texture image and continuously update this texture as long as video is playing."
+          "!doc": "An Image object, typically created using the [page:ImageLoader ImageLoader] class. The Image object can include an image (e.g., PNG, JPG, GIF, DDS), video (e.g., MP4, OGG/OGV), or set of six images for a cube map. To use video as a texture you need to have a playing HTML5 video element as a source for your texture image and continuously update this texture as long as video is playing."
         },
         "mapping": {
           "!type": "object",

+ 4 - 2
examples/canvas_geometry_panorama.html

@@ -81,8 +81,10 @@
 
 				];
 
-				mesh = new THREE.Mesh( new THREE.BoxGeometry( 300, 300, 300, 7, 7, 7 ), materials );
-				mesh.scale.x = - 1;
+				var geometry = new THREE.BoxGeometry( 300, 300, 300, 7, 7, 7 );
+				geometry.scale( - 1, 1, 1 );
+
+				mesh = new THREE.Mesh( geometry, materials );
 				scene.add( mesh );
 
 				renderer = new THREE.CanvasRenderer();

+ 4 - 2
examples/canvas_geometry_panorama_fisheye.html

@@ -81,8 +81,10 @@
 
 				];
 
-				mesh = new THREE.Mesh( new THREE.BoxGeometry( 300, 300, 300, 7, 7, 7 ), materials );
-				mesh.scale.x = - 1;
+				var geometry = new THREE.BoxGeometry( 300, 300, 300, 7, 7, 7 );
+				geometry.scale( - 1, 1, 1 );
+
+				mesh = new THREE.Mesh( geometry, materials );
 				scene.add( mesh );
 
 				for ( var i = 0, l = mesh.geometry.vertices.length; i < l; i ++ ) {

+ 0 - 2
examples/files.js

@@ -42,8 +42,6 @@ var files = {
 		"webgl_geometry_terrain_fog",
 		"webgl_geometry_terrain_raycast",
 		"webgl_geometry_text",
-		"webgl_geometry_text_earcut",
-		"webgl_geometry_text_pnltri",
 		"webgl_geometry_text_shapes",
 		"webgl_gpgpu_birds",
 		"webgl_gpgpu_water",

+ 229 - 32
examples/js/exporters/GLTFExporter.js

@@ -39,6 +39,13 @@ var THREE_TO_WEBGL = {
 	1008: WEBGL_CONSTANTS.LINEAR_MIPMAP_LINEAR
 };
 
+var PATH_PROPERTIES = {
+	scale: 'scale',
+	position: 'translation',
+	quaternion: 'rotation',
+	morphTargetInfluences: 'weights'
+};
+
 //------------------------------------------------------------------------------
 // GLTF Exporter
 //------------------------------------------------------------------------------
@@ -53,8 +60,6 @@ THREE.GLTFExporter.prototype = {
 	 * @param  {THREE.Scene or [THREE.Scenes]} input   THREE.Scene or Array of THREE.Scenes
 	 * @param  {Function} onDone  Callback on completed
 	 * @param  {Object} options options
-	 *                          trs: Exports position, rotation and scale instead of matrix
-	 *                          binary: Exports `.glb` as ArrayBuffer, instead of `.gltf` as JSON
 	 */
 	parse: function ( input, onDone, options ) {
 
@@ -62,11 +67,19 @@ THREE.GLTFExporter.prototype = {
 			trs: false,
 			onlyVisible: true,
 			truncateDrawRange: true,
-			embedImages: true
+			embedImages: true,
+			animations: []
 		};
 
 		options = Object.assign( {}, DEFAULT_OPTIONS, options );
 
+		if ( options.animations.length > 0 ) {
+
+			// Only TRS properties, and not matrices, may be targeted by animation.
+			options.trs = true;
+
+		}
+
 		var outputJSON = {
 
 			asset: {
@@ -80,6 +93,8 @@ THREE.GLTFExporter.prototype = {
 
 		var byteOffset = 0;
 		var dataViews = [];
+		var nodeMap = {};
+		var skins = [];
 		var cachedData = {
 
 			images: {},
@@ -136,8 +151,8 @@ THREE.GLTFExporter.prototype = {
 		}
 
 		/**
-		 * Get the min and he max vectors from the given attribute
-		 * @param  {THREE.WebGLAttribute} attribute Attribute to find the min/max
+		 * Get the min and max vectors from the given attribute
+		 * @param  {THREE.BufferAttribute} attribute Attribute to find the min/max
 		 * @return {Object} Object containing the `min` and `max` values (As an array of attribute.itemSize components)
 		 */
 		function getMinMax( attribute ) {
@@ -232,12 +247,14 @@ THREE.GLTFExporter.prototype = {
 
 		/**
 		 * Process and generate a BufferView
-		 * @param  {[type]} data [description]
-		 * @return {[type]}      [description]
+		 * @param  {THREE.BufferAttribute} data
+		 * @param  {number} componentType
+		 * @param  {number} start
+		 * @param  {number} count
+		 * @param  {number} target (Optional) Target usage of the BufferView
+		 * @return {Object}
 		 */
-		function processBufferView( data, componentType, start, count ) {
-
-			var isVertexAttributes = componentType === WEBGL_CONSTANTS.FLOAT;
+		function processBufferView( data, componentType, start, count, target ) {
 
 			if ( ! outputJSON.bufferViews ) {
 
@@ -255,11 +272,12 @@ THREE.GLTFExporter.prototype = {
 				buffer: processBuffer( data, componentType, start, count ),
 				byteOffset: byteOffset,
 				byteLength: byteLength,
-				byteStride: data.itemSize * componentSize,
-				target: isVertexAttributes ? WEBGL_CONSTANTS.ARRAY_BUFFER : WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER
+				byteStride: data.itemSize * componentSize
 
 			};
 
+			if ( target !== undefined ) gltfBufferView.target = target;
+
 			byteOffset += byteLength;
 
 			outputJSON.bufferViews.push( gltfBufferView );
@@ -278,7 +296,8 @@ THREE.GLTFExporter.prototype = {
 
 		/**
 		 * Process attribute to generate an accessor
-		 * @param  {THREE.WebGLAttribute} attribute Attribute to process
+		 * @param  {THREE.BufferAttribute} attribute Attribute to process
+		 * @param  {THREE.BufferGeometry} geometry (Optional) Geometry used for truncated draw range
 		 * @return {Integer}           Index of the processed accessor on the "accessors" array
 		 */
 		function processAccessor( attribute, geometry ) {
@@ -289,14 +308,15 @@ THREE.GLTFExporter.prototype = {
 
 			}
 
-			var types = [
+			var types = {
 
-				'SCALAR',
-				'VEC2',
-				'VEC3',
-				'VEC4'
+				1: 'SCALAR',
+				2: 'VEC2',
+				3: 'VEC3',
+				4: 'VEC4',
+				16: 'MAT4'
 
-			];
+			};
 
 			var componentType;
 
@@ -325,14 +345,25 @@ THREE.GLTFExporter.prototype = {
 			var count = attribute.count;
 
 			// @TODO Indexed buffer geometry with drawRange not supported yet
-			if ( options.truncateDrawRange && geometry.index === null ) {
+			if ( options.truncateDrawRange && geometry !== undefined && geometry.index === null ) {
 
 				start = geometry.drawRange.start;
 				count = geometry.drawRange.count !== Infinity ? geometry.drawRange.count : attribute.count;
 
 			}
 
-			var bufferView = processBufferView( attribute, componentType, start, count );
+			var bufferViewTarget;
+
+			// If geometry isn't provided, don't infer the target usage of the bufferView. For
+			// animation samplers, target must not be set.
+			if ( geometry !== undefined ) {
+
+				var isVertexAttributes = componentType === WEBGL_CONSTANTS.FLOAT;
+				bufferViewTarget = isVertexAttributes ? WEBGL_CONSTANTS.ARRAY_BUFFER : WEBGL_CONSTANTS.ELEMENT_ARRAY_BUFFER;
+
+			}
+
+			var bufferView = processBufferView( attribute, componentType, start, count, bufferViewTarget );
 
 			var gltfAccessor = {
 
@@ -342,7 +373,7 @@ THREE.GLTFExporter.prototype = {
 				count: count,
 				max: minMax.max,
 				min: minMax.min,
-				type: types[ attribute.itemSize - 1 ]
+				type: types[ attribute.itemSize ]
 
 			};
 
@@ -529,7 +560,7 @@ THREE.GLTFExporter.prototype = {
 
 				gltfMaterial.pbrMetallicRoughness.baseColorTexture = {
 
-					index: processTexture( material.map )
+					index: processTexture( material.map )
 
 				};
 
@@ -555,7 +586,7 @@ THREE.GLTFExporter.prototype = {
 
 					gltfMaterial.emissiveTexture = {
 
-						index: processTexture( material.emissiveMap )
+						index: processTexture( material.emissiveMap )
 
 					};
 
@@ -568,7 +599,7 @@ THREE.GLTFExporter.prototype = {
 
 				gltfMaterial.normalTexture = {
 
-					index: processTexture( material.normalMap )
+					index: processTexture( material.normalMap )
 
 				};
 
@@ -591,7 +622,7 @@ THREE.GLTFExporter.prototype = {
 
 				gltfMaterial.occlusionTexture = {
 
-					index: processTexture( material.aoMap )
+					index: processTexture( material.aoMap )
 
 				};
 
@@ -604,11 +635,12 @@ THREE.GLTFExporter.prototype = {
 			}
 
 			// alphaMode
-			if ( material.transparent ) {
+			if ( material.transparent || material.alphaTest > 0.0 ) {
 
-				gltfMaterial.alphaMode = 'MASK'; // @FIXME We should detect MASK or BLEND
+				gltfMaterial.alphaMode = material.opacity < 1.0 ? 'BLEND' : 'MASK';
 
-				if ( material.alphaTest !== 0.5 ) {
+				// Write alphaCutoff if it's non-zero and different from the default (0.5).
+				if ( material.alphaTest > 0.0 && material.alphaTest !== 0.5 ) {
 
 					gltfMaterial.alphaCutoff = material.alphaTest;
 
@@ -742,7 +774,35 @@ THREE.GLTFExporter.prototype = {
 
 				var attribute = geometry.attributes[ attributeName ];
 				attributeName = nameConversion[ attributeName ] || attributeName.toUpperCase();
-				gltfAttributes[ attributeName ] = processAccessor( attribute, geometry );
+
+				if ( attributeName.substr( 0, 5 ) !== 'MORPH' ) {
+
+					gltfAttributes[ attributeName ] = processAccessor( attribute, geometry );
+
+				}
+
+			}
+
+			// Morph targets
+			if ( mesh.morphTargetInfluences !== undefined && mesh.morphTargetInfluences.length > 0 ) {
+
+				gltfMesh.primitives[ 0 ].targets = [];
+
+				for ( var i = 0; i < mesh.morphTargetInfluences.length; ++ i ) {
+
+					var target = {};
+
+					for ( var attributeName in geometry.morphAttributes ) {
+
+						var attribute = geometry.morphAttributes[ attributeName ][ i ];
+						attributeName = nameConversion[ attributeName ] || attributeName.toUpperCase();
+						target[ attributeName ] = processAccessor( attribute, geometry );
+
+					}
+
+					gltfMesh.primitives[ 0 ].targets.push( target );
+
+				}
 
 			}
 
@@ -809,6 +869,123 @@ THREE.GLTFExporter.prototype = {
 
 		}
 
+		/**
+		 * Creates glTF animation entry from AnimationClip object.
+		 *
+		 * Status:
+		 * - Only properties listed in PATH_PROPERTIES may be animated.
+		 * - Only LINEAR and STEP interpolation currently supported.
+		 *
+		 * @param {THREE.AnimationClip} clip
+		 * @param {THREE.Object3D} root
+		 * @return {number}
+		 */
+		function processAnimation ( clip, root ) {
+
+			if ( ! outputJSON.animations ) {
+
+				outputJSON.animations = [];
+
+			}
+
+			var channels = [];
+			var samplers = [];
+
+			for ( var i = 0; i < clip.tracks.length; ++ i ) {
+
+				var track = clip.tracks[ i ];
+				var trackBinding = THREE.PropertyBinding.parseTrackName( track.name );
+				var trackNode = THREE.PropertyBinding.findNode( root, trackBinding.nodeName );
+				var trackProperty = PATH_PROPERTIES[ trackBinding.propertyName ];
+
+				if ( ! trackNode || ! trackProperty ) {
+
+					console.warn( 'THREE.GLTFExporter: Could not export animation track "%s".', track.name );
+					return null;
+
+				}
+
+				var inputItemSize = 1;
+				var outputItemSize = track.values.length / track.times.length;
+
+				if ( trackProperty === PATH_PROPERTIES.morphTargetInfluences ) {
+
+					outputItemSize /= trackNode.morphTargetInfluences.length;
+
+				}
+
+				samplers.push( {
+
+					input: processAccessor( new THREE.BufferAttribute( track.times, inputItemSize ) ),
+					output: processAccessor( new THREE.BufferAttribute( track.values, outputItemSize ) ),
+					interpolation: track.interpolation === THREE.InterpolateDiscrete ? 'STEP' : 'LINEAR'
+
+				} );
+
+				channels.push( {
+
+					sampler: samplers.length - 1,
+					target: {
+						node: nodeMap[ trackNode.uuid ],
+						path: trackProperty
+					}
+
+				} );
+
+			}
+
+			outputJSON.animations.push( {
+
+				name: clip.name || 'clip_' + outputJSON.animations.length,
+				samplers: samplers,
+				channels: channels
+
+			} );
+
+			return outputJSON.animations.length - 1;
+
+		}
+
+		function processSkin( object ) {
+
+			var node = outputJSON.nodes[ nodeMap[ object.uuid ] ];
+
+			var skeleton = object.skeleton;
+			var rootJoint = object.skeleton.bones[ 0 ];
+
+			if ( rootJoint === undefined ) return null;
+
+			var joints = [];
+			var inverseBindMatrices = new Float32Array( skeleton.bones.length * 16 );
+
+			for ( var i = 0; i < skeleton.bones.length; ++ i ) {
+
+				joints.push( nodeMap[ skeleton.bones[ i ].uuid ] );
+
+				skeleton.boneInverses[ i ].toArray( inverseBindMatrices, i * 16 );
+
+			}
+
+			if ( outputJSON.skins === undefined ) {
+
+				outputJSON.skins = [];
+
+			}
+
+			outputJSON.skins.push( {
+
+				inverseBindMatrices: processAccessor( new THREE.BufferAttribute( inverseBindMatrices, 16 ) ),
+				joints: joints,
+				skeleton: nodeMap[ rootJoint.uuid ]
+
+			} );
+
+			var skinIndex = node.skin = outputJSON.skins.length - 1;
+
+			return skinIndex;
+
+		}
+
 		/**
 		 * Process Object3D node
 		 * @param  {THREE.Object3D} node Object3D to processNode
@@ -845,7 +1022,7 @@ THREE.GLTFExporter.prototype = {
 
 				if ( ! equalArray( position, [ 0, 0, 0 ] ) ) {
 
-					gltfNode.position = position;
+					gltfNode.translation = position;
 
 				}
 
@@ -898,6 +1075,12 @@ THREE.GLTFExporter.prototype = {
 
 			}
 
+			if ( object instanceof THREE.SkinnedMesh ) {
+
+				skins.push( object );
+
+			}
+
 			if ( object.children.length > 0 ) {
 
 				var children = [];
@@ -931,7 +1114,9 @@ THREE.GLTFExporter.prototype = {
 
 			outputJSON.nodes.push( gltfNode );
 
-			return outputJSON.nodes.length - 1;
+			var nodeIndex = nodeMap[ object.uuid ] = outputJSON.nodes.length - 1;
+
+			return nodeIndex;
 
 		}
 
@@ -1037,6 +1222,18 @@ THREE.GLTFExporter.prototype = {
 
 			}
 
+			for ( var i = 0; i < skins.length; ++ i ) {
+
+				processSkin( skins[ i ] );
+
+			}
+
+			for ( var i = 0; i < options.animations.length; ++ i ) {
+
+				processAnimation( options.animations[ i ], input[ 0 ] );
+
+			}
+
 		}
 
 		processInput( input );

+ 0 - 654
examples/js/libs/earcut.js

@@ -1,654 +0,0 @@
-/**
- *
- * Earcut https://github.com/mapbox/earcut
- *
- * Copyright (c) 2016, Mapbox
- *
- * Permission to use, copy, modify, and/or distribute this software for any purpose
- * with or without fee is hereby granted, provided that the above copyright notice
- * and this permission notice appear in all copies.
- *
- * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
- * REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
- * FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
- * INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
- * OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
- * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF
- * THIS SOFTWARE.
- */
-'use strict';
-
-//module.exports = earcut;
-
-function earcut(data, holeIndices, dim) {
-
-    dim = dim || 2;
-
-    var hasHoles = holeIndices && holeIndices.length,
-        outerLen = hasHoles ? holeIndices[0] * dim : data.length,
-        outerNode = linkedList(data, 0, outerLen, dim, true),
-        triangles = [];
-
-    if (!outerNode) return triangles;
-
-    var minX, minY, maxX, maxY, x, y, size;
-
-    if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim);
-
-    // if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
-    if (data.length > 80 * dim) {
-        minX = maxX = data[0];
-        minY = maxY = data[1];
-
-        for (var i = dim; i < outerLen; i += dim) {
-            x = data[i];
-            y = data[i + 1];
-            if (x < minX) minX = x;
-            if (y < minY) minY = y;
-            if (x > maxX) maxX = x;
-            if (y > maxY) maxY = y;
-        }
-
-        // minX, minY and size are later used to transform coords into integers for z-order calculation
-        size = Math.max(maxX - minX, maxY - minY);
-    }
-
-    earcutLinked(outerNode, triangles, dim, minX, minY, size);
-
-    return triangles;
-}
-
-// create a circular doubly linked list from polygon points in the specified winding order
-function linkedList(data, start, end, dim, clockwise) {
-    var i, last;
-
-    if (clockwise === (signedArea(data, start, end, dim) > 0)) {
-        for (i = start; i < end; i += dim) last = insertNode(i, data[i], data[i + 1], last);
-    } else {
-        for (i = end - dim; i >= start; i -= dim) last = insertNode(i, data[i], data[i + 1], last);
-    }
-
-    if (last && equals(last, last.next)) {
-        removeNode(last);
-        last = last.next;
-    }
-
-    return last;
-}
-
-// eliminate colinear or duplicate points
-function filterPoints(start, end) {
-    if (!start) return start;
-    if (!end) end = start;
-
-    var p = start,
-        again;
-    do {
-        again = false;
-
-        if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) {
-            removeNode(p);
-            p = end = p.prev;
-            if (p === p.next) return null;
-            again = true;
-
-        } else {
-            p = p.next;
-        }
-    } while (again || p !== end);
-
-    return end;
-}
-
-// main ear slicing loop which triangulates a polygon (given as a linked list)
-function earcutLinked(ear, triangles, dim, minX, minY, size, pass) {
-    if (!ear) return;
-
-    // interlink polygon nodes in z-order
-    if (!pass && size) indexCurve(ear, minX, minY, size);
-
-    var stop = ear,
-        prev, next;
-
-    // iterate through ears, slicing them one by one
-    while (ear.prev !== ear.next) {
-        prev = ear.prev;
-        next = ear.next;
-
-        if (size ? isEarHashed(ear, minX, minY, size) : isEar(ear)) {
-            // cut off the triangle
-            triangles.push(prev.i / dim);
-            triangles.push(ear.i / dim);
-            triangles.push(next.i / dim);
-
-            removeNode(ear);
-
-            // skipping the next vertice leads to less sliver triangles
-            ear = next.next;
-            stop = next.next;
-
-            continue;
-        }
-
-        ear = next;
-
-        // if we looped through the whole remaining polygon and can't find any more ears
-        if (ear === stop) {
-            // try filtering points and slicing again
-            if (!pass) {
-                earcutLinked(filterPoints(ear), triangles, dim, minX, minY, size, 1);
-
-            // if this didn't work, try curing all small self-intersections locally
-            } else if (pass === 1) {
-                ear = cureLocalIntersections(ear, triangles, dim);
-                earcutLinked(ear, triangles, dim, minX, minY, size, 2);
-
-            // as a last resort, try splitting the remaining polygon into two
-            } else if (pass === 2) {
-                splitEarcut(ear, triangles, dim, minX, minY, size);
-            }
-
-            break;
-        }
-    }
-}
-
-// check whether a polygon node forms a valid ear with adjacent nodes
-function isEar(ear) {
-    var a = ear.prev,
-        b = ear,
-        c = ear.next;
-
-    if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
-
-    // now make sure we don't have other points inside the potential ear
-    var p = ear.next.next;
-
-    while (p !== ear.prev) {
-        if (pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
-            area(p.prev, p, p.next) >= 0) return false;
-        p = p.next;
-    }
-
-    return true;
-}
-
-function isEarHashed(ear, minX, minY, size) {
-    var a = ear.prev,
-        b = ear,
-        c = ear.next;
-
-    if (area(a, b, c) >= 0) return false; // reflex, can't be an ear
-
-    // triangle bbox; min & max are calculated like this for speed
-    var minTX = a.x < b.x ? (a.x < c.x ? a.x : c.x) : (b.x < c.x ? b.x : c.x),
-        minTY = a.y < b.y ? (a.y < c.y ? a.y : c.y) : (b.y < c.y ? b.y : c.y),
-        maxTX = a.x > b.x ? (a.x > c.x ? a.x : c.x) : (b.x > c.x ? b.x : c.x),
-        maxTY = a.y > b.y ? (a.y > c.y ? a.y : c.y) : (b.y > c.y ? b.y : c.y);
-
-    // z-order range for the current triangle bbox;
-    var minZ = zOrder(minTX, minTY, minX, minY, size),
-        maxZ = zOrder(maxTX, maxTY, minX, minY, size);
-
-    // first look for points inside the triangle in increasing z-order
-    var p = ear.nextZ;
-
-    while (p && p.z <= maxZ) {
-        if (p !== ear.prev && p !== ear.next &&
-            pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
-            area(p.prev, p, p.next) >= 0) return false;
-        p = p.nextZ;
-    }
-
-    // then look for points in decreasing z-order
-    p = ear.prevZ;
-
-    while (p && p.z >= minZ) {
-        if (p !== ear.prev && p !== ear.next &&
-            pointInTriangle(a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y) &&
-            area(p.prev, p, p.next) >= 0) return false;
-        p = p.prevZ;
-    }
-
-    return true;
-}
-
-// go through all polygon nodes and cure small local self-intersections
-function cureLocalIntersections(start, triangles, dim) {
-    var p = start;
-    do {
-        var a = p.prev,
-            b = p.next.next;
-
-        if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) {
-
-            triangles.push(a.i / dim);
-            triangles.push(p.i / dim);
-            triangles.push(b.i / dim);
-
-            // remove two nodes involved
-            removeNode(p);
-            removeNode(p.next);
-
-            p = start = b;
-        }
-        p = p.next;
-    } while (p !== start);
-
-    return p;
-}
-
-// try splitting polygon into two and triangulate them independently
-function splitEarcut(start, triangles, dim, minX, minY, size) {
-    // look for a valid diagonal that divides the polygon into two
-    var a = start;
-    do {
-        var b = a.next.next;
-        while (b !== a.prev) {
-            if (a.i !== b.i && isValidDiagonal(a, b)) {
-                // split the polygon in two by the diagonal
-                var c = splitPolygon(a, b);
-
-                // filter colinear points around the cuts
-                a = filterPoints(a, a.next);
-                c = filterPoints(c, c.next);
-
-                // run earcut on each half
-                earcutLinked(a, triangles, dim, minX, minY, size);
-                earcutLinked(c, triangles, dim, minX, minY, size);
-                return;
-            }
-            b = b.next;
-        }
-        a = a.next;
-    } while (a !== start);
-}
-
-// link every hole into the outer loop, producing a single-ring polygon without holes
-function eliminateHoles(data, holeIndices, outerNode, dim) {
-    var queue = [],
-        i, len, start, end, list;
-
-    for (i = 0, len = holeIndices.length; i < len; i++) {
-        start = holeIndices[i] * dim;
-        end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
-        list = linkedList(data, start, end, dim, false);
-        if (list === list.next) list.steiner = true;
-        queue.push(getLeftmost(list));
-    }
-
-    queue.sort(compareX);
-
-    // process holes from left to right
-    for (i = 0; i < queue.length; i++) {
-        eliminateHole(queue[i], outerNode);
-        outerNode = filterPoints(outerNode, outerNode.next);
-    }
-
-    return outerNode;
-}
-
-function compareX(a, b) {
-    return a.x - b.x;
-}
-
-// find a bridge between vertices that connects hole with an outer ring and and link it
-function eliminateHole(hole, outerNode) {
-    outerNode = findHoleBridge(hole, outerNode);
-    if (outerNode) {
-        var b = splitPolygon(outerNode, hole);
-        filterPoints(b, b.next);
-    }
-}
-
-// David Eberly's algorithm for finding a bridge between hole and outer polygon
-function findHoleBridge(hole, outerNode) {
-    var p = outerNode,
-        hx = hole.x,
-        hy = hole.y,
-        qx = -Infinity,
-        m;
-
-    // find a segment intersected by a ray from the hole's leftmost point to the left;
-    // segment's endpoint with lesser x will be potential connection point
-    do {
-        if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) {
-            var x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y);
-            if (x <= hx && x > qx) {
-                qx = x;
-                if (x === hx) {
-                    if (hy === p.y) return p;
-                    if (hy === p.next.y) return p.next;
-                }
-                m = p.x < p.next.x ? p : p.next;
-            }
-        }
-        p = p.next;
-    } while (p !== outerNode);
-
-    if (!m) return null;
-
-    if (hx === qx) return m.prev; // hole touches outer segment; pick lower endpoint
-
-    // look for points inside the triangle of hole point, segment intersection and endpoint;
-    // if there are no points found, we have a valid connection;
-    // otherwise choose the point of the minimum angle with the ray as connection point
-
-    var stop = m,
-        mx = m.x,
-        my = m.y,
-        tanMin = Infinity,
-        tan;
-
-    p = m.next;
-
-    while (p !== stop) {
-        if (hx >= p.x && p.x >= mx && hx !== p.x &&
-                pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) {
-
-            tan = Math.abs(hy - p.y) / (hx - p.x); // tangential
-
-            if ((tan < tanMin || (tan === tanMin && p.x > m.x)) && locallyInside(p, hole)) {
-                m = p;
-                tanMin = tan;
-            }
-        }
-
-        p = p.next;
-    }
-
-    return m;
-}
-
-// interlink polygon nodes in z-order
-function indexCurve(start, minX, minY, size) {
-    var p = start;
-    do {
-        if (p.z === null) p.z = zOrder(p.x, p.y, minX, minY, size);
-        p.prevZ = p.prev;
-        p.nextZ = p.next;
-        p = p.next;
-    } while (p !== start);
-
-    p.prevZ.nextZ = null;
-    p.prevZ = null;
-
-    sortLinked(p);
-}
-
-// Simon Tatham's linked list merge sort algorithm
-// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
-function sortLinked(list) {
-    var i, p, q, e, tail, numMerges, pSize, qSize,
-        inSize = 1;
-
-    do {
-        p = list;
-        list = null;
-        tail = null;
-        numMerges = 0;
-
-        while (p) {
-            numMerges++;
-            q = p;
-            pSize = 0;
-            for (i = 0; i < inSize; i++) {
-                pSize++;
-                q = q.nextZ;
-                if (!q) break;
-            }
-            qSize = inSize;
-
-            while (pSize > 0 || (qSize > 0 && q)) {
-
-                if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) {
-                    e = p;
-                    p = p.nextZ;
-                    pSize--;
-                } else {
-                    e = q;
-                    q = q.nextZ;
-                    qSize--;
-                }
-
-                if (tail) tail.nextZ = e;
-                else list = e;
-
-                e.prevZ = tail;
-                tail = e;
-            }
-
-            p = q;
-        }
-
-        tail.nextZ = null;
-        inSize *= 2;
-
-    } while (numMerges > 1);
-
-    return list;
-}
-
-// z-order of a point given coords and size of the data bounding box
-function zOrder(x, y, minX, minY, size) {
-    // coords are transformed into non-negative 15-bit integer range
-    x = 32767 * (x - minX) / size;
-    y = 32767 * (y - minY) / size;
-
-    x = (x | (x << 8)) & 0x00FF00FF;
-    x = (x | (x << 4)) & 0x0F0F0F0F;
-    x = (x | (x << 2)) & 0x33333333;
-    x = (x | (x << 1)) & 0x55555555;
-
-    y = (y | (y << 8)) & 0x00FF00FF;
-    y = (y | (y << 4)) & 0x0F0F0F0F;
-    y = (y | (y << 2)) & 0x33333333;
-    y = (y | (y << 1)) & 0x55555555;
-
-    return x | (y << 1);
-}
-
-// find the leftmost node of a polygon ring
-function getLeftmost(start) {
-    var p = start,
-        leftmost = start;
-    do {
-        if (p.x < leftmost.x) leftmost = p;
-        p = p.next;
-    } while (p !== start);
-
-    return leftmost;
-}
-
-// check if a point lies within a convex triangle
-function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) {
-    return (cx - px) * (ay - py) - (ax - px) * (cy - py) >= 0 &&
-           (ax - px) * (by - py) - (bx - px) * (ay - py) >= 0 &&
-           (bx - px) * (cy - py) - (cx - px) * (by - py) >= 0;
-}
-
-// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
-function isValidDiagonal(a, b) {
-    return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) &&
-           locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b);
-}
-
-// signed area of a triangle
-function area(p, q, r) {
-    return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
-}
-
-// check if two points are equal
-function equals(p1, p2) {
-    return p1.x === p2.x && p1.y === p2.y;
-}
-
-// check if two segments intersect
-function intersects(p1, q1, p2, q2) {
-    if ((equals(p1, q1) && equals(p2, q2)) ||
-        (equals(p1, q2) && equals(p2, q1))) return true;
-    return area(p1, q1, p2) > 0 !== area(p1, q1, q2) > 0 &&
-           area(p2, q2, p1) > 0 !== area(p2, q2, q1) > 0;
-}
-
-// check if a polygon diagonal intersects any polygon segments
-function intersectsPolygon(a, b) {
-    var p = a;
-    do {
-        if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&
-                intersects(p, p.next, a, b)) return true;
-        p = p.next;
-    } while (p !== a);
-
-    return false;
-}
-
-// check if a polygon diagonal is locally inside the polygon
-function locallyInside(a, b) {
-    return area(a.prev, a, a.next) < 0 ?
-        area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 :
-        area(a, b, a.prev) < 0 || area(a, a.next, b) < 0;
-}
-
-// check if the middle point of a polygon diagonal is inside the polygon
-function middleInside(a, b) {
-    var p = a,
-        inside = false,
-        px = (a.x + b.x) / 2,
-        py = (a.y + b.y) / 2;
-    do {
-        if (((p.y > py) !== (p.next.y > py)) && p.next.y !== p.y &&
-                (px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x))
-            inside = !inside;
-        p = p.next;
-    } while (p !== a);
-
-    return inside;
-}
-
-// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;
-// if one belongs to the outer ring and another to a hole, it merges it into a single ring
-function splitPolygon(a, b) {
-    var a2 = new Node(a.i, a.x, a.y),
-        b2 = new Node(b.i, b.x, b.y),
-        an = a.next,
-        bp = b.prev;
-
-    a.next = b;
-    b.prev = a;
-
-    a2.next = an;
-    an.prev = a2;
-
-    b2.next = a2;
-    a2.prev = b2;
-
-    bp.next = b2;
-    b2.prev = bp;
-
-    return b2;
-}
-
-// create a node and optionally link it with previous one (in a circular doubly linked list)
-function insertNode(i, x, y, last) {
-    var p = new Node(i, x, y);
-
-    if (!last) {
-        p.prev = p;
-        p.next = p;
-
-    } else {
-        p.next = last.next;
-        p.prev = last;
-        last.next.prev = p;
-        last.next = p;
-    }
-    return p;
-}
-
-function removeNode(p) {
-    p.next.prev = p.prev;
-    p.prev.next = p.next;
-
-    if (p.prevZ) p.prevZ.nextZ = p.nextZ;
-    if (p.nextZ) p.nextZ.prevZ = p.prevZ;
-}
-
-function Node(i, x, y) {
-    // vertice index in coordinates array
-    this.i = i;
-
-    // vertex coordinates
-    this.x = x;
-    this.y = y;
-
-    // previous and next vertice nodes in a polygon ring
-    this.prev = null;
-    this.next = null;
-
-    // z-order curve value
-    this.z = null;
-
-    // previous and next nodes in z-order
-    this.prevZ = null;
-    this.nextZ = null;
-
-    // indicates whether this is a steiner point
-    this.steiner = false;
-}
-
-// return a percentage difference between the polygon area and its triangulation area;
-// used to verify correctness of triangulation
-earcut.deviation = function (data, holeIndices, dim, triangles) {
-    var hasHoles = holeIndices && holeIndices.length;
-    var outerLen = hasHoles ? holeIndices[0] * dim : data.length;
-
-    var polygonArea = Math.abs(signedArea(data, 0, outerLen, dim));
-    if (hasHoles) {
-        for (var i = 0, len = holeIndices.length; i < len; i++) {
-            var start = holeIndices[i] * dim;
-            var end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
-            polygonArea -= Math.abs(signedArea(data, start, end, dim));
-        }
-    }
-
-    var trianglesArea = 0;
-    for (i = 0; i < triangles.length; i += 3) {
-        var a = triangles[i] * dim;
-        var b = triangles[i + 1] * dim;
-        var c = triangles[i + 2] * dim;
-        trianglesArea += Math.abs(
-            (data[a] - data[c]) * (data[b + 1] - data[a + 1]) -
-            (data[a] - data[b]) * (data[c + 1] - data[a + 1]));
-    }
-
-    return polygonArea === 0 && trianglesArea === 0 ? 0 :
-        Math.abs((trianglesArea - polygonArea) / polygonArea);
-};
-
-function signedArea(data, start, end, dim) {
-    var sum = 0;
-    for (var i = start, j = end - dim; i < end; i += dim) {
-        sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]);
-        j = i;
-    }
-    return sum;
-}
-
-// turn a polygon in a multi-dimensional array form (e.g. as in GeoJSON) into a form Earcut accepts
-earcut.flatten = function (data) {
-    var dim = data[0][0].length,
-        result = {vertices: [], holes: [], dimensions: dim},
-        holeIndex = 0;
-
-    for (var i = 0; i < data.length; i++) {
-        for (var j = 0; j < data[i].length; j++) {
-            for (var d = 0; d < dim; d++) result.vertices.push(data[i][j][d]);
-        }
-        if (i > 0) {
-            holeIndex += data[i - 1].length;
-            result.holes.push(holeIndex);
-        }
-    }
-    return result;
-};

+ 0 - 19
examples/js/libs/pnltri.min.js

@@ -1,19 +0,0 @@
-// pnltri.js / raw.github.com/jahting/pnltri.js/master/LICENSE
-'use strict';var r={ca:"2.0"};r.c={random:Math.random,da:function(b){for(var c=b.length-1;0<c;c--){var l=Math.floor(r.c.random()*(c+1)),k=b[c];b[c]=b[l];b[l]=k}return b},G:function(b,c){var l=b.y-c.y;if(l<r.c.D)return-1;if(l>r.c.n)return 1;l=b.x-c.x;return l<r.c.D?-1:l>r.c.n?1:0},N:function(b,c,l){return(c.x-b.x)*(l.y-b.y)-(c.y-b.y)*(l.x-b.x)}};r.c.n=Math.pow(2,-43);r.c.D=-r.c.n;function u(b){this.U=[];this.r=[];this.X=[];this.L=0;this.F=[];this.R=[];this.T=[];if(b)for(var c=0,l=b.length;c<l;c++){var k=w(this,b[c]);if(3>k.length)console.log("Polygon has < 3 vertices!",k);else{for(var a=void 0,e=void 0,d=void 0,f=0;f<k.length-1;f++)a=x(this,k[f],k[f+1]),d?(a.o=d,d.m=a):e=a,d=a,this.r.push(a);a=x(this,k[k.length-1],k[0]);a.o=d;d.m=a;this.r.push(a);e.o=a;a.m=e;this.F[this.L++]=!0}}}u.prototype={Q:function(){return this.F.concat()}};
-function B(b,c,l,k){b.T.push([c.id,l.id,k.id])}
-function w(b,c){function l(a,b){return Math.abs(a.x-b.x)<r.c.n&&Math.abs(a.y-b.y)<r.c.n}function k(a,b,c){if(Math.abs(r.c.N(b,a,c))>r.c.n)return!1;var d;Math.abs(a.y-b.y)<r.c.n?(d=b.x,a.x<c.x?(b=a.x,a=c.x):(b=c.x,a=a.x)):(d=b.y,a.y<c.y?(b=a.y,a=c.y):(b=c.y,a=a.y));return b-d<r.c.n&&d-a<r.c.n}for(var a=[],e,d,f,h=0;h<c.length;h++)e=D(b,c[h].x,c[h].y),d=!0,f=a.length-1,0<=f&&(l(e,a[f])?d=!1:0<f&&k(a[f-1],a[f],e)&&a.pop()),d&&a.push(e);f=a.length-1;0<f&&l(a[f],a[0])&&(a.pop(),f--);1<f&&(k(a[f-1],a[f],
-a[0])&&(a.pop(),f--),1<f&&k(a[f],a[0],a[1])&&a.shift());return a}function x(b,c,l){return{O:b.L,a:c,i:l,p:1==r.c.G(l,c),o:null,m:null,H:null,I:null,v:!1,J:null,K:null,j:null,g:null,w:!1}}function D(b,c,l){c={id:b.U.length,x:c,y:l};b.U.push(c);return c};function E(b){this.k=b}E.prototype={};function F(b,c,l,k){this.q=b?b:{x:Number.POSITIVE_INFINITY,y:Number.POSITIVE_INFINITY};this.l=c?c:{x:Number.NEGATIVE_INFINITY,y:Number.NEGATIVE_INFINITY};this.t=l;this.s=k;this.depth=-1}F.prototype={};function G(b){var c=new F(b.q,b.l,b.t,b.s);c.e=b.e;c.f=b.f;c.b=b.b;c.d=b.d;c.u=b.u;return c}function H(b){this.A=b;b.u=this}H.prototype={};function I(b){var c=new F(null,null,null,null);this.B=[];J(this,c);this.root=new H(c);if(b)for(b=b.r,c=0;c<b.length;c++)b[c].H=b[c].I=this.root,b[c].v=!1}
-I.prototype={P:function(b){var c,l,k=b.U.length,a=Array(k);for(c=0;c<k;c++)a[c]=Array(8);var e=Array(k);c=0;for(l=this.B.length;c<l;c++){var d=this.B[c],f=d.e?d.f?5:7:d.f?4:6,h=d.b?d.d?1:0:d.d?3:2;if(1==d.depth%2){if(5==f||1==h||7==f&&3==h||4==f&&0==h){var g;g={a:d.l,i:d.q,j:null,g:null,w:!1};b.X.push(g);var p;p={a:d.q,i:d.l,aa:g,j:null,g:null,w:!1};b.X.push(p);g.aa=p;a[d.l.id][h]=g;a[d.q.id][f]=p}}else null!=d.q.id&&(e[d.q.id]=f),null!=d.l.id&&(e[d.l.id]=h)}var t;for(c=0;c<k;c++)if(d=a[c],f=e[c],
-null!=f){l=f;h=null;do if(7<l++&&(l=0),b=d[l])h?(b.j=h,h.g=b):(t=b.a,t.Y=b),h=b.aa;while(l!=f);h&&(t.Z=h)}}};
-function L(b,c){function l(){var a=m.e||m.f;a.b&&a.d?m==a.b?(n.e=null,a.b=q):(q.f=null,a.d=n):(n.e=null,n.f=a,a.d=n)}function k(a){m.l==t.l?(f?m.b?(a.e=q,q.b=a,n.d=null):(a.f=n,q.b=null,n.d=a):(a.e=q,a.f=n,q.b=n.d=a),q.d=n.b=null):(a.e&&a.f&&(a.e==m?(a.C=a.f,a.ba=!0):(a.C=a.e,a.ba=!1)),a.e=q,a.f=n,q.d=n.b=a,q.b=n.d=null)}function a(){var a;if(m.l==t.l&&f)m.b.e=q,m.d.f=n,q.b=m.b,n.d=m.d,a=q.d=n.b=null;else{m.b.e=q;m.d.f=n;var b=M(c,m.l);if(0<b)a=!0;else if(0>b)a=!1;else{a=m.b.s;var d=a.p,b=d?a.a:a.i,
-b=M(c,b);0<b?a=!0:0>b?a=!1:(a=d?a.m:a.o,b=d?a.i:a.a,b=M(c,b),a=0<b?!0:!1)}a?(a=m.d,m.d.e=q,q.b=m.b,n.d=null):(a=m.b,m.b.f=n,n.d=m.d,q.b=null);q.d=n.b=a}return a}N(c);var e,d,f,h,g,p;c.p?(e=c.a,h=c.i,d=c.H,g=c.I,f=c.o.v,p=c.m.v):(e=c.i,h=c.a,d=c.I,g=c.H,f=c.m.v,p=c.o.v);p||(p=O(b,g,h,!1),g==d&&(d=p),g=p);g=g.A;if(g.e||g.f)if(g.q!=h)console.log("ERR add_segment: trFirstHigh != segHigh: ",g);else{f||(d=O(b,d,e,!0));var t=d.A,m=g,q,n,y,v;for(e=b.B.length+2;m;){if(0>--e){console.log("ERR add_segment: infinite loop",
-m,c,b);return}if(!m.b&&!m.d){console.log("ERR add_segment: missing successors",m,c,b);return}d=m.u;d.h=c;d.A=null;v&&v.s==m.s?(q=m,n=v,n.l=m.l,d.left=new H(q),d.right=v.u):(y&&y.t==m.t?(n=m,q=y,q.l=m.l,d.left=y.u):(q=m,n=P(b,m),d.left=new H(q)),d.right=new H(n));m.e&&m.f?m.C?(m.ba?(n.e=m.f,n.f=m.C,n.e.b=n,n.f.d=n):(q.f=m.e,q.e=m.C,q.e.b=q,q.f.d=q),q.C=n.C=null):m.q==g.q?(n.f.d=n,q.f=n.e=null):n==m?(n.e=n.f,n.f=null,n.e.b=n):(q.f=q.e,q.e=null):l();m.b&&m.d?d=a():(d=m.b?m.b:m.d,k(d));q.s&&(q.s.J=n);
-n.t&&(n.t.K=q);q.s=n.t=c;c.J=q;c.K=n;m.l!=t.l?(y=q,v=n,m=d):m=null}c.v=!0}else console.log("ERR add_segment: missing trFirst.uX: ",g)}
-function Q(b,c){if(c)var l=b.a,k=b.i,a=b.H;else l=b.i,k=b.a,a=b.I;for(var e;a;)if(a.V)a=-1==r.c.G(l==a.V?k:l,a.V)?a.left:a.right;else if(a.h){if(l==a.h.a||l==a.h.i)if(Math.abs(l.y-k.y)<r.c.n){a=Math.abs(a.h.a.y-a.h.i.y)<r.c.n?l==a.h.a?((e=b.p?k.x>=a.h.i.x:k.x<a.h.i.x)?b.o.p:a.h.m.p)?a.right:a.left:((e=b.p?k.x<a.h.a.x:k.x>=a.h.a.x)?b.m.p:a.h.o.p)?a.left:a.right:k.x<l.x?a.left:a.right;continue}else e=M(a.h,k),0==e&&(e=l==a.h.a?(e=b.p?k.y>=a.h.i.y:k.y<a.h.i.y)?M(a.h,b.o.a):-M(a.h,a.h.m.i):(e=b.p?k.y<
-a.h.a.y:k.y>=a.h.a.y)?M(a.h,b.m.i):-M(a.h,a.h.o.a));else e=M(a.h,l),0==e&&(e=M(a.h,k),0==e&&(e=M(a.h,c?b.o.a:b.m.i)));if(0<e)a=a.left;else if(0>e)a=a.right;else break}else{a.A||console.log("ptNode: unknown type",a);c?b.H=a:b.I=a;break}}function N(b){Q(b,!0);Q(b,!1)}function M(b,c){var l;l=b.a.x-c.x;var k=b.i.x-c.x,a=Math.abs(b.a.y-c.y)<r.c.n;if(Math.abs(b.i.y-c.y)<r.c.n){if(a)return 0;l=k}else if(!a)return b.p?r.c.N(b.a,b.i,c):r.c.N(b.i,b.a,c);return Math.abs(l)<r.c.n?0:l}
-function O(b,c,l,k){var a=c.A;if(a.q==l||a.l==l)return c;var e=G(a);a.l=e.q=l;a.b=e;e.e=a;a.d=e.f=null;e.b&&(e.b.e=e);e.d&&(e.d.f=e);J(b,e);c.V=l;c.A=null;c.right=new H(a);c.left=new H(e);return k?a.u:e.u}function P(b,c){var l=G(c);J(b,l);return l}function J(b,c){c.fa=b.B.length;b.B.push(c)}function V(b){this.k=b;this.$=new I(this.k)}V.prototype={P:function(){return this.$.P(this.k)}};function W(b){this.k=b;this.S=null}W.prototype={};function X(b){this.k=b}X.prototype={};function Z(){this.M=null}
-Z.prototype={W:function(){this.M=null},Q:function(){return this.M?this.M.Q():null},ea:function(b,c){this.W();if(!b||0==b.length)return[];var l=new u(b),k=c?!1:1==l.L;if(k){k=new E(l);a:{var a=k.k,e=a.r[0],d=e,f=e,h=0;do h+=(f.a.x-f.i.x)*(f.a.y+f.i.y),f=f.m;while(f!=e);if(0>h){do d.j=d.m,d=d.g=d.o;while(d!=e);a.F[0]=!1}else{do d.j=d.o,d=d.g=d.m;while(d!=e)}for(e=a=e;a.g!=a.j;){b:{var d=a.j.a.x,f=a.j.a.y,h=a.a.x,g=a.a.y,p=a.g.a.x,t=a.g.a.y,m=p-h,q=t-g,n=d-p,y=f-t,v=h-d,K=g-f;if(r.c.n>v*q-m*K)d=!1;else{for(var Y=
-a.j.j,C=a.g;C!=Y;){var C=C.g,z=C.a.x,A=C.a.y,R=z-d,S=A-f;if(0!=R||0!=S){var T=z-h,U=A-g;if(0!=T||0!=U)if(z-=p,A-=t,(0!=z||0!=A)&&m*U-q*T>=r.c.D&&v*S-K*R>=r.c.D&&n*A-y*z>=r.c.D){d=!1;break b}}}d=!0}}if(d)B(k.k,a.j.a,a.a,a.g.a),a.j.g=a.g,a.g.j=a.j,e=a=a.g;else if(a=a.g,a==e){k=!1;break a}}k=!0}}if(!k){k=new W(l);k.S=new V(k.k);e=k.S;a=e.k.r.concat();r.c.da(a);d=0;f=e.k.L;if(1!=f)for(h=Array(f),g=a.concat(),p=0;p<g.length;p++)t=g[p].O,h[t]?a[f++]=g[p]:(a[d++]=g[p],h[t]=!0);d=a.length;f=e.$;h=0;for(g=
-d;h<d;){g=Math.log(g)/Math.LN2;for(p=1<g?Math.floor(d/g):d;h<p;h++)L(f,a[h]);for(p=h;p<d;p++)N(a[p])}var e=e.k,f=[f.B[0]],h=[],s,p=0;do{for(t=1==p%2;g=f.pop();)-1==g.depth&&(g.depth=p,g.e&&f.push(g.e),g.f&&f.push(g.f),g.b&&f.push(g.b),g.d&&f.push(g.d),(s=g.t)&&-1==s.J.depth&&h.push(s.J),s=g.s)&&(-1==s.K.depth&&h.push(s.K),s.p!=t&&(e.F[s.O]=!1));f=h;h=[];p++}while(0<f.length);for(p=0;p<d;p++)a[p].J=a[p].K=null;k.S.P();s=k.k;d=0;for(f=s.r.length;d<f;d++){a=s.r[d];s.F[a.O]?(e=a.i,a.j=a.o,a.g=a.m):(e=
-a.a,a=a.m,a.j=a.m,a.g=a.o);if(h=a.a.Z)h.g=a,a.j=h,a.a.Z=null;if(h=e.Y)h.j=a,a.g=h,e.Y=null}s=k.k;s.R=[];k=0;for(a=s.r.length;k<a;k++)if(e=s.r[k],!e.w){a:{h=f=d=void 0;g=e;f=h=e.a;e.w=!0;for(e=e.g;d=e.a;){if(e.w){if(d==f)break;console.log("ERR unique_monotone: segment in two chains",f,e);e=null;break a}e.w=!0;1==r.c.G(d,h)&&(h=d,g=e);e=e.g}e=g}e&&s.R.push(e)}s=k=new X(l);k=s.k.R;s.k.T=[];for(a=0;a<k.length;a++)if(f=k[a],e=f.j,d=f.g,d.g==e)B(s.k,f.a,d.a,e.a);else if(e=s,d=f.g,f=f.a,h=[d.a],g=0,d=d.g,
-p=d.a,p!=f){for(;p!=f||1<g;)if(0<g)if(t=r.c.N(h[g],p,h[g-1]),Math.abs(t)<=r.c.n&&(p==f||r.c.G(h[g],p)==r.c.G(h[g],h[g-1]))&&(t=1),0<t)B(e.k,h[g-1],h[g],p),g--;else if(h[++g]=p,p==f)for(console.log("ERR uni-y-monotone: only concave angles left",h);1<g;)g--,B(e.k,h[g-1],h[g],h[g+1]);else d=d.g,p=d.a;else h[++g]=p,d=d.g,p=d.a;B(e.k,h[g-1],h[g],p)}}this.M=l;return l.T.concat()}};window.PNLTRI=r;r.REVISION=r.ca;r.Math=r.c;r.Triangulator=Z;Z.prototype.clear_lastData=Z.prototype.W;Z.prototype.get_PolyLeftArr=Z.prototype.Q;Z.prototype.triangulate_polygon=Z.prototype.ea;

+ 7 - 1
examples/js/loaders/ColladaLoader.js

@@ -19,7 +19,7 @@ THREE.ColladaLoader.prototype = {
 
 		var scope = this;
 
-		var path = THREE.Loader.prototype.extractUrlBase( url );
+		var path = scope.path === undefined ? THREE.Loader.prototype.extractUrlBase( url ) : scope.path;
 
 		var loader = new THREE.FileLoader( scope.manager );
 		loader.load( url, function ( text ) {
@@ -30,6 +30,12 @@ THREE.ColladaLoader.prototype = {
 
 	},
 
+	setPath: function ( value ) {
+
+		this.path = value;
+
+	},
+
 	options: {
 
 		set convertUpAxis( value ) {

File diff suppressed because it is too large
+ 503 - 586
examples/js/loaders/FBXLoader.js


+ 78 - 16
examples/js/loaders/GLTFLoader.js

@@ -108,7 +108,7 @@ THREE.GLTFLoader = ( function () {
 
 			if ( json.asset === undefined || json.asset.version[ 0 ] < 2 ) {
 
-				if ( onError ) onError( new Error( 'THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported.' ) );
+				if ( onError ) onError( new Error( 'THREE.GLTFLoader: Unsupported asset. glTF versions >=2.0 are supported. Use LegacyGLTFLoader instead.' ) );
 				return;
 
 			}
@@ -424,7 +424,7 @@ THREE.GLTFLoader = ( function () {
 
 		} else if ( this.header.version < 2.0 ) {
 
-			throw new Error( 'THREE.GLTFLoader: Legacy binary file detected. Use GLTFLoader instead.' );
+			throw new Error( 'THREE.GLTFLoader: Legacy binary file detected. Use LegacyGLTFLoader instead.' );
 
 		}
 
@@ -532,6 +532,7 @@ THREE.GLTFLoader = ( function () {
 					'vec3 specularFactor = specular;',
 					'#ifdef USE_SPECULARMAP',
 					'	vec4 texelSpecular = texture2D( specularMap, vUv );',
+					'	texelSpecular = sRGBToLinear( texelSpecular );',
 					'	// reads channel RGB, compatible with a glTF Specular-Glossiness (RGBA) texture',
 					'	specularFactor *= texelSpecular.rgb;',
 					'#endif'
@@ -1018,7 +1019,7 @@ THREE.GLTFLoader = ( function () {
 
 	/* UTILITY FUNCTIONS */
 
-	function _each( object, callback, thisObj ) {
+	function _each( object, callback ) {
 
 		if ( ! object ) {
 
@@ -1037,7 +1038,7 @@ THREE.GLTFLoader = ( function () {
 
 			for ( var idx = 0; idx < length; idx ++ ) {
 
-				var value = callback.call( thisObj || this, object[ idx ], idx );
+				var value = callback( object[ idx ], idx );
 
 				if ( value ) {
 
@@ -1069,7 +1070,7 @@ THREE.GLTFLoader = ( function () {
 
 				if ( object.hasOwnProperty( key ) ) {
 
-					var value = callback.call( thisObj || this, object[ key ], key );
+					var value = callback( object[ key ], key );
 
 					if ( value ) {
 
@@ -1385,7 +1386,6 @@ THREE.GLTFLoader = ( function () {
 		for ( var i = 0; i < dependencies.length; i ++ ) {
 
 			var dependency = dependencies[ i ];
-			var fnName = 'load' + dependency.charAt( 0 ).toUpperCase() + dependency.slice( 1 );
 
 			var cached = this.cache.get( dependency );
 
@@ -1393,8 +1393,9 @@ THREE.GLTFLoader = ( function () {
 
 				_dependencies[ dependency ] = cached;
 
-			} else if ( this[ fnName ] ) {
+			} else {
 
+				var fnName = 'load' + dependency.charAt( 0 ).toUpperCase() + dependency.slice( 1 );
 				var fn = this[ fnName ]();
 				this.cache.add( dependency, fn );
 
@@ -1546,7 +1547,28 @@ THREE.GLTFLoader = ( function () {
 
 		return _each( json.accessors, function ( accessor ) {
 
-			return parser.getDependency( 'bufferView', accessor.bufferView ).then( function ( bufferView ) {
+			var pendingBufferViews = [];
+
+			if ( accessor.bufferView !== undefined ) {
+
+				pendingBufferViews.push( parser.getDependency( 'bufferView', accessor.bufferView ) );
+
+			} else {
+
+				pendingBufferViews.push( null );
+
+			}
+
+			if ( accessor.sparse !== undefined ) {
+
+				pendingBufferViews.push( parser.getDependency( 'bufferView', accessor.sparse.indices.bufferView ) );
+				pendingBufferViews.push( parser.getDependency( 'bufferView', accessor.sparse.values.bufferView ) );
+
+			}
+
+			return Promise.all( pendingBufferViews ).then( function ( bufferViews ) {
+
+				var bufferView = bufferViews[ 0 ];
 
 				var itemSize = WEBGL_TYPE_SIZES[ accessor.type ];
 				var TypedArray = WEBGL_COMPONENT_TYPES[ accessor.componentType ];
@@ -1556,7 +1578,7 @@ THREE.GLTFLoader = ( function () {
 				var itemBytes = elementBytes * itemSize;
 				var byteStride = json.bufferViews[ accessor.bufferView ].byteStride;
 				var normalized = accessor.normalized === true;
-				var array;
+				var array, bufferAttribute;
 
 				// The buffer is not interleaved if the stride is the item size in bytes.
 				if ( byteStride && byteStride !== itemBytes ) {
@@ -1567,16 +1589,59 @@ THREE.GLTFLoader = ( function () {
 					// Integer parameters to IB/IBA are in array elements, not bytes.
 					var ib = new THREE.InterleavedBuffer( array, byteStride / elementBytes );
 
-					return new THREE.InterleavedBufferAttribute( ib, itemSize, accessor.byteOffset / elementBytes, normalized );
+					bufferAttribute = new THREE.InterleavedBufferAttribute( ib, itemSize, accessor.byteOffset / elementBytes, normalized );
 
 				} else {
 
-					array = new TypedArray( bufferView, accessor.byteOffset, accessor.count * itemSize );
+					if ( bufferView === null ) {
+
+						array = new TypedArray( accessor.count * itemSize );
 
-					return new THREE.BufferAttribute( array, itemSize, normalized );
+					} else {
+
+						array = new TypedArray( bufferView, accessor.byteOffset, accessor.count * itemSize );
+
+					}
+
+					bufferAttribute = new THREE.BufferAttribute( array, itemSize, normalized );
+
+				}
+
+				// https://github.com/KhronosGroup/glTF/blob/master/specification/2.0/README.md#sparse-accessors
+				if ( accessor.sparse !== undefined ) {
+
+					var itemSizeIndices = WEBGL_TYPE_SIZES.SCALAR;
+					var TypedArrayIndices = WEBGL_COMPONENT_TYPES[ accessor.sparse.indices.componentType ];
+
+					var byteOffsetIndices = accessor.sparse.indices.byteOffset || 0;
+					var byteOffsetValues = accessor.sparse.values.byteOffset || 0;
+
+					var sparseIndices = new TypedArrayIndices( bufferViews[ 1 ], byteOffsetIndices, accessor.sparse.count * itemSizeIndices );
+					var sparseValues = new TypedArray( bufferViews[ 2 ], byteOffsetValues, accessor.sparse.count * itemSize );
+
+					if ( bufferView !== null ) {
+
+						// Avoid modifying the original ArrayBuffer, if the bufferView wasn't initialized with zeroes.
+						bufferAttribute.setArray( bufferAttribute.array.slice() );
+
+					}
+
+					for ( var i = 0; i < sparseIndices.length; i++ ) {
+
+						var index = sparseIndices[ i ];
+
+						bufferAttribute.setX( index, sparseValues[ i * itemSize ] );
+						if ( itemSize >= 2 ) bufferAttribute.setY( index, sparseValues[ i * itemSize + 1 ] );
+						if ( itemSize >= 3 ) bufferAttribute.setZ( index, sparseValues[ i * itemSize + 2 ] );
+						if ( itemSize >= 4 ) bufferAttribute.setW( index, sparseValues[ i * itemSize + 3 ] );
+						if ( itemSize >= 5 ) throw new Error( 'THREE.GLTFLoader: Unsupported itemSize in sparse BufferAttribute.' );
+
+					}
 
 				}
 
+				return bufferAttribute;
+
 			} );
 
 		} );
@@ -1906,8 +1971,6 @@ THREE.GLTFLoader = ( function () {
 
 					var attributeEntry = attributes[ attributeId ];
 
-					if ( attributeEntry === undefined ) return;
-
 					var bufferAttribute = dependencies.accessors[ attributeEntry ];
 
 					switch ( attributeId ) {
@@ -2368,8 +2431,7 @@ THREE.GLTFLoader = ( function () {
 		return scope._withDependencies( [
 
 			'meshes',
-			'skins',
-			'cameras'
+			'skins'
 
 		] ).then( function ( dependencies ) {
 

+ 289 - 228
examples/js/loaders/LoaderSupport.js

@@ -941,13 +941,8 @@ THREE.LoaderSupport.WorkerRunnerRefImpl = (function () {
 	 * @param {Object} payload Raw mesh description (buffers, params, materials) used to build one to many meshes.
 	 */
 	WorkerRunnerRefImpl.prototype.processMessage = function ( payload ) {
-		var logger = new ConsoleLogger();
-		if ( Validator.isValid( payload.logger ) ) {
-
-			logger.setEnabled( payload.logger.enabled );
-			logger.setDebug( payload.logger.debug );
-
-		}
+		var logEnabled = payload.logger.enabled;
+		var logDebug = payload.logger.enabled;
 		if ( payload.cmd === 'run' ) {
 
 			var callbacks = {
@@ -955,19 +950,20 @@ THREE.LoaderSupport.WorkerRunnerRefImpl = (function () {
 					self.postMessage( payload );
 				},
 				callbackProgress: function ( text ) {
-					logger.logDebug( 'WorkerRunner: progress: ' + text );
+					if ( logEnabled && logDebug ) console.debug( 'WorkerRunner: progress: ' + text );
 				}
 			};
 
 			// Parser is expected to be named as such
-			var parser = new Parser( logger );
+			var parser = new Parser();
+			if ( typeof parser[ 'setLogConfig' ] === 'function' ) parser.setLogConfig( logEnabled, logDebug );
 			this.applyProperties( parser, payload.params );
 			this.applyProperties( parser, payload.materials );
 			this.applyProperties( parser, callbacks );
 			parser.workerScope = self;
 			parser.parse( payload.data.input, payload.data.options );
 
-			logger.logInfo( 'WorkerRunner: Run complete!' );
+			if ( logEnabled ) console.log( 'WorkerRunner: Run complete!' );
 
 			callbacks.callbackBuilder( {
 				cmd: 'complete',
@@ -976,7 +972,7 @@ THREE.LoaderSupport.WorkerRunnerRefImpl = (function () {
 
 		} else {
 
-			logger.logError( 'WorkerRunner: Received unknown command: ' + payload.cmd );
+			console.error( 'WorkerRunner: Received unknown command: ' + payload.cmd );
 
 		}
 	};
@@ -993,170 +989,226 @@ THREE.LoaderSupport.WorkerRunnerRefImpl = (function () {
  */
 THREE.LoaderSupport.WorkerSupport = (function () {
 
-	var WORKER_SUPPORT_VERSION = '1.1.1';
+	var WORKER_SUPPORT_VERSION = '2.0.0';
 
 	var Validator = THREE.LoaderSupport.Validator;
 
-	function WorkerSupport( logger ) {
-		this.logger = Validator.verifyInput( logger, new THREE.LoaderSupport.ConsoleLogger() );
-		this.logger.logInfo( 'Using THREE.LoaderSupport.WorkerSupport version: ' + WORKER_SUPPORT_VERSION );
+	var LoaderWorker = (function () {
 
-		// check worker support first
-		if ( window.Worker === undefined ) throw "This browser does not support web workers!";
-		if ( window.Blob === undefined  ) throw "This browser does not support Blob!";
-		if ( typeof window.URL.createObjectURL !== 'function'  ) throw "This browser does not support Object creation from URL!";
+		function LoaderWorker( logger ) {
+			this.logger = Validator.verifyInput( logger, new THREE.LoaderSupport.ConsoleLogger() );
+			this._reset();
+		}
+
+		LoaderWorker.prototype._reset = function () {
+			this.worker = null;
+			this.runnerImplName = null;
+			this.callbacks = {
+				builder: null,
+				onLoad: null
+			};
+			this.terminateRequested = false;
+			this.queuedMessage = null;
+			this.started = false;
+		};
+
+		LoaderWorker.prototype.initWorker = function ( code, runnerImplName ) {
+			this.runnerImplName = runnerImplName;
+			var blob = new Blob( [ code ], { type: 'application/javascript' } );
+			this.worker = new Worker( window.URL.createObjectURL( blob ) );
+			this.worker.onmessage = this._receiveWorkerMessage;
 
-		this.worker = null;
-		this.workerCode = null;
-		this.loading = true;
-		this.queuedMessage = null;
-		this.running = false;
-		this.terminateRequested = false;
+			// set referemce to this, then processing in worker scope within "_receiveWorkerMessage" can access members
+			this.worker.runtimeRef = this;
 
-		this.callbacks = {
-			builder: null,
-			onLoad: null
+			// process stored queuedMessage
+			this._postMessage();
 		};
-	}
 
-	/**
-	 * Validate the status of worker code and the derived worker.
-	 * @memberOf THREE.LoaderSupport.WorkerSupport
-	 *
-	 * @param {Function} functionCodeBuilder Function that is invoked with funcBuildObject and funcBuildSingelton that allows stringification of objects and singletons.
-	 * @param {boolean} forceWorkerReload Force re-build of the worker code.
-	 * @param {String[]} libLocations URL of libraries that shall be added to worker code relative to libPath
-	 * @param {String} libPath Base path used for loading libraries
-	 * @param {THREE.LoaderSupport.WorkerRunnerRefImpl} runnerImpl The default worker parser wrapper implementation (communication and execution). An extended class could be passed here.
-	 */
-	WorkerSupport.prototype.validate = function ( functionCodeBuilder, forceWorkerReload, libLocations, libPath, runnerImpl ) {
-		this.running = false;
-		if ( forceWorkerReload ) {
+		/**
+		 * Executed in worker scope
+ 		 */
+		LoaderWorker.prototype._receiveWorkerMessage = function ( e ) {
+			var payload = e.data;
+			switch ( payload.cmd ) {
+				case 'meshData':
+				case 'materialData':
+				case 'imageData':
+					this.runtimeRef.callbacks.builder( payload );
+					break;
 
-			this.worker = null;
-			this.workerCode = null;
-			this.loading = true;
-			this.queuedMessage = null;
-			this.callbacks.builder = null;
-			this.callbacks.onLoad = null;
+				case 'complete':
+					this.runtimeRef.queuedMessage = null;
+					this.started = false;
+					this.runtimeRef.callbacks.onLoad( payload.msg );
 
-		}
+					if ( this.runtimeRef.terminateRequested ) {
+
+						this.runtimeRef.logger.logInfo( 'WorkerSupport [' + this.runtimeRef.runnerImplName + ']: Run is complete. Terminating application on request!' );
+						this.runtimeRef._terminate();
+
+					}
+					break;
+
+				case 'error':
+					this.runtimeRef.logger.logError( 'WorkerSupport [' + this.runtimeRef.runnerImplName + ']: Reported error: ' + payload.msg );
+					this.runtimeRef.queuedMessage = null;
+					this.started = false;
+					this.runtimeRef.callbacks.onLoad( payload.msg );
 
-		if ( ! Validator.isValid( this.worker ) ) {
+					if ( this.runtimeRef.terminateRequested ) {
 
-			this.logger.logInfo( 'WorkerSupport: Building worker code...' );
-			this.logger.logTimeStart( 'buildWebWorkerCode' );
+						this.runtimeRef.logger.logInfo( 'WorkerSupport [' + this.runtimeRef.runnerImplName + ']: Run reported error. Terminating application on request!' );
+						this.runtimeRef._terminate();
 
-			var workerRunner;
-			if ( Validator.isValid( runnerImpl ) ) {
+					}
+					break;
 
-				this.logger.logInfo( 'WorkerSupport: Using "' + runnerImpl.name + '" as Runncer class for worker.' );
-				workerRunner = runnerImpl;
+				default:
+					this.runtimeRef.logger.logError( 'WorkerSupport [' + this.runtimeRef.runnerImplName + ']: Received unknown command: ' + payload.cmd );
+					break;
+
+			}
+		};
+
+		LoaderWorker.prototype.setCallbacks = function ( builder, onLoad ) {
+			this.callbacks.builder = Validator.verifyInput( builder, this.callbacks.builder );
+			this.callbacks.onLoad = Validator.verifyInput( onLoad, this.callbacks.onLoad );
+		};
+
+		LoaderWorker.prototype.run = function( payload ) {
+			if ( Validator.isValid( this.queuedMessage ) ) {
+
+				console.warn( 'Already processing message. Rejecting new run instruction' );
+				return;
 
 			} else {
 
-				this.logger.logInfo( 'WorkerSupport: Using DEFAULT "THREE.LoaderSupport.WorkerRunnerRefImpl" as Runncer class for worker.' );
-				workerRunner = THREE.LoaderSupport.WorkerRunnerRefImpl;
+				this.queuedMessage = payload;
+				this.started = true;
 
 			}
+			if ( ! Validator.isValid( this.callbacks.builder ) ) throw 'Unable to run as no "builder" callback is set.';
+			if ( ! Validator.isValid( this.callbacks.onLoad ) ) throw 'Unable to run as no "onLoad" callback is set.';
+			if ( payload.cmd !== 'run' ) payload.cmd = 'run';
+			if ( Validator.isValid( payload.logger ) ) {
 
-			var scope = this;
-			var buildWorkerCode = function ( baseWorkerCode ) {
-				scope.workerCode = baseWorkerCode;
-				if ( workerRunner == THREE.LoaderSupport.WorkerRunnerRefImpl ) {
+				payload.logger.enabled = Validator.verifyInput( payload.logger.enabled, true );
+				payload.logger.debug = Validator.verifyInput( payload.logger.debug, false );
 
-					scope.workerCode += buildObject( 'Validator', THREE.LoaderSupport.Validator );
-					scope.workerCode += buildSingelton( 'ConsoleLogger', 'ConsoleLogger', THREE.LoaderSupport.ConsoleLogger );
+			} else {
 
+				payload.logger = {
+					enabled: true,
+					debug: false
 				}
-				scope.workerCode += functionCodeBuilder( buildObject, buildSingelton );
-				scope.workerCode += buildSingelton( workerRunner.name, workerRunner.name, workerRunner );
-				scope.workerCode += 'new ' + workerRunner.name + '();\n\n';
 
-				var blob = new Blob( [ scope.workerCode ], { type: 'application/javascript' } );
-				scope.worker = new Worker( window.URL.createObjectURL( blob ) );
-				scope.logger.logTimeEnd( 'buildWebWorkerCode' );
+			}
+			this._postMessage();
+		};
 
-				var receiveWorkerMessage = function ( e ) {
-					var payload = e.data;
+		LoaderWorker.prototype._postMessage = function () {
+			if ( Validator.isValid( this.queuedMessage ) && Validator.isValid( this.worker ) ) {
 
-					switch ( payload.cmd ) {
-						case 'meshData':
-						case 'materialData':
-						case 'imageData':
-							scope.callbacks.builder( payload );
-							break;
+				this.worker.postMessage( this.queuedMessage );
 
-						case 'complete':
-							scope.callbacks.onLoad( payload.msg );
-							scope.running = false;
+			}
+		};
 
-							if ( scope.terminateRequested ) {
+		LoaderWorker.prototype.setTerminateRequested = function ( terminateRequested ) {
+			this.terminateRequested = terminateRequested === true;
+			if ( this.terminateRequested && Validator.isValid( this.worker ) && ! Validator.isValid( this.queuedMessage ) && this.started ) {
 
-								scope.logger.logInfo( 'WorkerSupport [' + workerRunner + ']: Run is complete. Terminating application on request!' );
-								scope.terminateWorker();
+				this.logger.logInfo( 'Worker is terminated immediately as it is not running!' );
+				this._terminate();
 
-							}
-							break;
+			}
+		};
 
-						case 'error':
-							scope.logger.logError( 'WorkerSupport [' + workerRunner + ']: Reported error: ' + payload.msg );
-							break;
+		LoaderWorker.prototype._terminate = function () {
+			this.worker.terminate();
+			this._reset();
+		};
 
-						default:
-							scope.logger.logError( 'WorkerSupport [' + workerRunner + ']: Received unknown command: ' + payload.cmd );
-							break;
+		return LoaderWorker;
 
-					}
-				};
-				scope.worker.addEventListener( 'message', receiveWorkerMessage, false );
-				scope.loading = false;
-				scope._postMessage();
-			};
+	})();
 
-			if ( Validator.isValid( libLocations ) && libLocations.length > 0 ) {
+	function WorkerSupport( logger ) {
+		this.logger = Validator.verifyInput( logger, new THREE.LoaderSupport.ConsoleLogger() );
+		this.logger.logInfo( 'Using THREE.LoaderSupport.WorkerSupport version: ' + WORKER_SUPPORT_VERSION );
 
-				var libsContent = '';
-				var loadAllLibraries = function ( path, locations ) {
-					if ( locations.length === 0 ) {
+		// check worker support first
+		if ( window.Worker === undefined ) throw "This browser does not support web workers!";
+		if ( window.Blob === undefined  ) throw "This browser does not support Blob!";
+		if ( typeof window.URL.createObjectURL !== 'function'  ) throw "This browser does not support Object creation from URL!";
 
-						buildWorkerCode( libsContent );
+		this.loaderWorker = new LoaderWorker( this.logger );
+	}
 
-					} else {
+	/**
+	 * Validate the status of worker code and the derived worker.
+	 * @memberOf THREE.LoaderSupport.WorkerSupport
+	 *
+	 * @param {Function} functionCodeBuilder Function that is invoked with funcBuildObject and funcBuildSingelton that allows stringification of objects and singletons.
+	 * @param {String[]} libLocations URL of libraries that shall be added to worker code relative to libPath
+	 * @param {String} libPath Base path used for loading libraries
+	 * @param {THREE.LoaderSupport.WorkerRunnerRefImpl} runnerImpl The default worker parser wrapper implementation (communication and execution). An extended class could be passed here.
+	 */
+	WorkerSupport.prototype.validate = function ( functionCodeBuilder, libLocations, libPath, runnerImpl ) {
+		if ( Validator.isValid( this.loaderWorker.worker ) ) return;
 
-						var loadedLib = function ( contentAsString ) {
-							libsContent += contentAsString;
-							loadAllLibraries( path, locations );
-						};
+		this.logger.logInfo( 'WorkerSupport: Building worker code...' );
+		this.logger.logTimeStart( 'buildWebWorkerCode' );
 
-						var fileLoader = new THREE.FileLoader();
-						fileLoader.setPath( path );
-						fileLoader.setResponseType( 'text' );
-						fileLoader.load( locations[ 0 ], loadedLib );
-						locations.shift();
+		if ( Validator.isValid( runnerImpl ) ) {
 
-					}
-				};
-				loadAllLibraries( libPath, libLocations );
+			this.logger.logInfo( 'WorkerSupport: Using "' + runnerImpl.name + '" as Runncer class for worker.' );
 
-			} else {
+		} else {
 
-				buildWorkerCode( '' );
+			runnerImpl = THREE.LoaderSupport.WorkerRunnerRefImpl;
+			this.logger.logInfo( 'WorkerSupport: Using DEFAULT "THREE.LoaderSupport.WorkerRunnerRefImpl" as Runncer class for worker.' );
 
-			}
 		}
-	};
 
-	/**
-	 * Terminate the worker and the code.
-	 * @memberOf THREE.LoaderSupport.WorkerSupport
-	 */
-	WorkerSupport.prototype.terminateWorker = function () {
-		if ( Validator.isValid( this.worker ) ) {
-			this.worker.terminate();
+		var userWorkerCode = functionCodeBuilder( buildObject, buildSingelton );
+		userWorkerCode += buildSingelton( runnerImpl.name, runnerImpl.name, runnerImpl );
+		userWorkerCode += 'new ' + runnerImpl.name + '();\n\n';
+
+		var scope = this;
+		if ( Validator.isValid( libLocations ) && libLocations.length > 0 ) {
+
+			var libsContent = '';
+			var loadAllLibraries = function ( path, locations ) {
+				if ( locations.length === 0 ) {
+
+					scope.loaderWorker.initWorker( libsContent + userWorkerCode, scope.logger, runnerImpl.name );
+					scope.logger.logTimeEnd( 'buildWebWorkerCode' );
+
+				} else {
+
+					var loadedLib = function ( contentAsString ) {
+						libsContent += contentAsString;
+						loadAllLibraries( path, locations );
+					};
+
+					var fileLoader = new THREE.FileLoader();
+					fileLoader.setPath( path );
+					fileLoader.setResponseType( 'text' );
+					fileLoader.load( locations[ 0 ], loadedLib );
+					locations.shift();
+
+				}
+			};
+			loadAllLibraries( libPath, libLocations );
+
+		} else {
+
+			this.loaderWorker.initWorker( userWorkerCode, this.logger, runnerImpl.name );
+			this.logger.logTimeEnd( 'buildWebWorkerCode' );
+
 		}
-		this.worker = null;
-		this.workerCode = null;
 	};
 
 	/**
@@ -1167,10 +1219,27 @@ THREE.LoaderSupport.WorkerSupport = (function () {
 	 * @param {Function} onLoad The function that is called when parsing is complete.
 	 */
 	WorkerSupport.prototype.setCallbacks = function ( builder, onLoad ) {
-		this.callbacks = {
-			builder: builder,
-			onLoad: onLoad
-		};
+		this.loaderWorker.setCallbacks( builder, onLoad );
+	};
+
+	/**
+	 * Runs the parser with the provided configuration.
+	 * @memberOf THREE.LoaderSupport.WorkerSupport
+	 *
+	 * @param {Object} payload Raw mesh description (buffers, params, materials) used to build one to many meshes.
+	 */
+	WorkerSupport.prototype.run = function ( payload ) {
+		this.loaderWorker.run( payload );
+	};
+
+	/**
+	 * Request termination of worker once parser is finished.
+	 * @memberOf THREE.LoaderSupport.WorkerSupport
+	 *
+	 * @param {boolean} terminateRequested True or false.
+	 */
+	WorkerSupport.prototype.setTerminateRequested = function ( terminateRequested ) {
+		this.loaderWorker.setTerminateRequested( terminateRequested );
 	};
 
 	var buildObject = function ( fullName, object ) {
@@ -1229,41 +1298,8 @@ THREE.LoaderSupport.WorkerSupport = (function () {
 		return objectString;
 	};
 
-	/**
-	 * Request termination of worker once parser is finished.
-	 * @memberOf THREE.LoaderSupport.WorkerSupport
-	 *
-	 * @param {boolean} terminateRequested True or false.
-	 */
-	WorkerSupport.prototype.setTerminateRequested = function ( terminateRequested ) {
-		this.terminateRequested = terminateRequested === true;
-	};
-
-	/**
-	 * Runs the parser with the provided configuration.
-	 * @memberOf THREE.LoaderSupport.WorkerSupport
-	 *
-	 * @param {Object} payload Raw mesh description (buffers, params, materials) used to build one to many meshes.
-	 */
-	WorkerSupport.prototype.run = function ( payload ) {
-		if ( ! Validator.isValid( this.callbacks.builder ) ) throw 'Unable to run as no "builder" callback is set.';
-		if ( ! Validator.isValid( this.callbacks.onLoad ) ) throw 'Unable to run as no "onLoad" callback is set.';
-		if ( Validator.isValid( this.worker ) || this.loading ) {
-			if ( payload.cmd !== 'run' ) payload.cmd = 'run';
-			this.queuedMessage = payload;
-			this.running = true;
-			this._postMessage();
-
-		}
-	};
-
-	WorkerSupport.prototype._postMessage = function () {
-		if ( ! this.loading && Validator.isValid( this.queuedMessage ) ) {
-			this.worker.postMessage( this.queuedMessage );
-		}
-	};
-
 	return WorkerSupport;
+
 })();
 
 /**
@@ -1272,7 +1308,7 @@ THREE.LoaderSupport.WorkerSupport = (function () {
  *   prepareWorkers
  *   enqueueForRun
  *   processQueue
- *   deregister
+ *   tearDown (to force stop)
  *
  * @class
  *
@@ -1281,7 +1317,7 @@ THREE.LoaderSupport.WorkerSupport = (function () {
  */
 THREE.LoaderSupport.WorkerDirector = (function () {
 
-	var LOADER_WORKER_DIRECTOR_VERSION = '2.0.0';
+	var LOADER_WORKER_DIRECTOR_VERSION = '2.1.0';
 
 	var Validator = THREE.LoaderSupport.Validator;
 
@@ -1301,10 +1337,13 @@ THREE.LoaderSupport.WorkerDirector = (function () {
 		this.workerDescription = {
 			classDef: classDef,
 			globalCallbacks: {},
-			workerSupports: []
+			workerSupports: {}
 		};
 		this.objectsCompleted = 0;
 		this.instructionQueue = [];
+		this.instructionQueuePointer = 0;
+
+		this.callbackOnFinishedProcessing = null;
 	}
 
 	/**
@@ -1349,30 +1388,21 @@ THREE.LoaderSupport.WorkerDirector = (function () {
 		if ( Validator.isValid( globalCallbacks ) ) this.workerDescription.globalCallbacks = globalCallbacks;
 		this.maxQueueSize = Math.min( maxQueueSize, MAX_QUEUE_SIZE );
 		this.maxWebWorkers = Math.min( maxWebWorkers, MAX_WEB_WORKER );
+		this.maxWebWorkers = Math.min( this.maxWebWorkers, this.maxQueueSize );
 		this.objectsCompleted = 0;
 		this.instructionQueue = [];
+		this.instructionQueuePointer = 0;
 
-		var start = this.workerDescription.workerSupports.length;
-		var i;
-		if ( start < this.maxWebWorkers ) {
-
-			for ( i = start; i < this.maxWebWorkers; i++ ) {
-
-				this.workerDescription.workerSupports[ i ] = {
-					workerSupport: new THREE.LoaderSupport.WorkerSupport( this.logger ),
-					loader: null
-				};
-
-			}
-
-		} else {
+		for ( var instanceNo = 0; instanceNo < this.maxWebWorkers; instanceNo++ ) {
 
-			for ( i = start - 1; i >= this.maxWebWorkers; i-- ) {
+			this.workerDescription.workerSupports[ instanceNo ] = {
+				instanceNo: instanceNo,
+				inUse: false,
+				terminateRequested: false,
+				workerSupport: new THREE.LoaderSupport.WorkerSupport( this.logger ),
+				loader: null
+			};
 
-				this.workerDescription.workerSupports[ i ].workerSupport.setRequestTerminate( true );
-				this.workerDescription.workerSupports.pop();
-
-			}
 		}
 	};
 
@@ -1388,47 +1418,68 @@ THREE.LoaderSupport.WorkerDirector = (function () {
 		}
 	};
 
+	/**
+	 * Returns if any workers are running.
+	 *
+	 * @memberOf THREE.LoaderSupport.WorkerDirector
+	 * @returns {boolean}
+	 */
+	WorkerDirector.prototype.isRunning = function () {
+		var wsKeys = Object.keys( this.workerDescription.workerSupports );
+		return ( ( this.instructionQueue.length > 0 && this.instructionQueuePointer < this.instructionQueue.length ) || wsKeys.length > 0 );
+	};
+
 	/**
 	 * Process the instructionQueue until it is depleted.
 	 * @memberOf THREE.LoaderSupport.WorkerDirector
 	 */
 	WorkerDirector.prototype.processQueue = function () {
-		if ( this.instructionQueue.length === 0 ) return;
+		var prepData, supportDesc;
+		for ( var instanceNo in this.workerDescription.workerSupports ) {
 
-		var length = Math.min( this.maxWebWorkers, this.instructionQueue.length );
-		for ( var i = 0; i < length; i++ ) {
+			supportDesc = this.workerDescription.workerSupports[ instanceNo ];
+			if ( ! supportDesc.inUse ) {
 
-			this._kickWorkerRun( this.instructionQueue[ 0 ], i );
-			this.instructionQueue.shift();
+				if ( this.instructionQueuePointer < this.instructionQueue.length ) {
 
-		}
-	};
+					prepData = this.instructionQueue[ this.instructionQueuePointer ];
+					this._kickWorkerRun( prepData, supportDesc );
+					this.instructionQueuePointer++;
 
-	WorkerDirector.prototype._kickWorkerRun = function( prepData, workerInstanceNo ) {
-		var scope = this;
-		var directorOnLoad = function ( event ) {
-			scope.objectsCompleted++;
+				} else {
 
-			var nextPrepData = scope.instructionQueue[ 0 ];
-			if ( Validator.isValid( nextPrepData ) ) {
+					this._deregister( supportDesc );
 
-				scope.instructionQueue.shift();
-				scope.logger.logInfo( '\nAssigning next item from queue to worker (queue length: ' + scope.instructionQueue.length + ')\n\n' );
-				scope._kickWorkerRun( nextPrepData, event.detail.instanceNo );
+				}
 
-			} else if ( scope.instructionQueue.length === 0 ) {
+			}
 
-				scope.deregister();
+		}
 
-			}
-		};
+		if ( ! this.isRunning() && this.callbackOnFinishedProcessing !== null ) {
+
+			this.callbackOnFinishedProcessing();
+			this.callbackOnFinishedProcessing = null;
 
+		}
+	};
+
+	WorkerDirector.prototype._kickWorkerRun = function( prepData, supportDesc ) {
+		supportDesc.inUse = true;
+		supportDesc.workerSupport.setTerminateRequested( supportDesc.terminateRequested );
+
+		this.logger.logInfo( '\nAssigning next item from queue to worker (queue length: ' + this.instructionQueue.length + ')\n\n' );
+
+		var scope = this;
 		var prepDataCallbacks = prepData.getCallbacks();
 		var globalCallbacks = this.workerDescription.globalCallbacks;
 		var wrapperOnLoad = function ( event ) {
 			if ( Validator.isValid( globalCallbacks.onLoad ) ) globalCallbacks.onLoad( event );
 			if ( Validator.isValid( prepDataCallbacks.onLoad ) ) prepDataCallbacks.onLoad( event );
-			directorOnLoad( event );
+			scope.objectsCompleted++;
+			supportDesc.inUse = false;
+
+			scope.processQueue();
 		};
 
 		var wrapperOnProgress = function ( event ) {
@@ -1441,8 +1492,7 @@ THREE.LoaderSupport.WorkerDirector = (function () {
 			if ( Validator.isValid( prepDataCallbacks.onMeshAlter ) ) prepDataCallbacks.onMeshAlter( event );
 		};
 
-		var supportTuple = this.workerDescription.workerSupports[ workerInstanceNo ];
-		supportTuple.loader = this._buildLoader( workerInstanceNo );
+		supportDesc.loader = this._buildLoader( supportDesc.instanceNo );
 
 		var updatedCallbacks = new THREE.LoaderSupport.Callbacks();
 		updatedCallbacks.setCallbackOnLoad( wrapperOnLoad );
@@ -1450,13 +1500,13 @@ THREE.LoaderSupport.WorkerDirector = (function () {
 		updatedCallbacks.setCallbackOnMeshAlter( wrapperOnMeshAlter );
 		prepData.callbacks = updatedCallbacks;
 
-		supportTuple.loader.run( prepData, supportTuple.workerSupport );
+		supportDesc.loader.run( prepData, supportDesc.workerSupport );
 	};
 
 	WorkerDirector.prototype._buildLoader = function ( instanceNo ) {
 		var classDef = this.workerDescription.classDef;
 		var loader = Object.create( classDef.prototype );
-		this.workerDescription.classDef.call( loader, null, this.logger );
+		this.workerDescription.classDef.call( loader, THREE.DefaultLoadingManager, this.logger );
 
 		// verify that all required functions are implemented
 		if ( ! loader.hasOwnProperty( 'instanceNo' ) ) throw classDef.name + ' has no property "instanceNo".';
@@ -1466,36 +1516,47 @@ THREE.LoaderSupport.WorkerDirector = (function () {
 
 			throw classDef.name + ' has no property "workerSupport".';
 
-		} else if ( ! classDef.workerSupport instanceof THREE.LoaderSupport.WorkerSupport ) {
-
-			throw classDef.name + '.workerSupport is not of type "THREE.LoaderSupport.WorkerSupport".';
-
 		}
 		if ( typeof loader.run !== 'function'  ) throw classDef.name + ' has no function "run".';
+		if ( ! loader.hasOwnProperty( 'callbacks' ) || ! Validator.isValid( loader.callbacks ) ) {
+
+			this.logger.logWarn( classDef.name + ' has an invalid property "callbacks". Will change to "THREE.LoaderSupport.Callbacks"' );
+			loader.callbacks = new THREE.LoaderSupport.Callbacks();
 
+		}
 		return loader;
 	};
 
+	WorkerDirector.prototype._deregister = function ( supportDesc ) {
+		if ( Validator.isValid( supportDesc ) ) {
+
+			supportDesc.workerSupport.setTerminateRequested( true );
+			this.logger.logInfo( 'Requested termination of worker #' + supportDesc.instanceNo + '.' );
+
+			var loaderCallbacks = supportDesc.loader.callbacks;
+			if ( Validator.isValid( loaderCallbacks.onProgress ) ) loaderCallbacks.onProgress( { detail: { text: '' } } );
+			delete this.workerDescription.workerSupports[ supportDesc.instanceNo ];
+
+		}
+	};
+
 	/**
 	 * Terminate all workers.
 	 * @memberOf THREE.LoaderSupport.WorkerDirector
+	 *
+	 * @param {callback} callbackOnFinishedProcessing Function called once all workers finished processing.
 	 */
-	WorkerDirector.prototype.deregister = function () {
+	WorkerDirector.prototype.tearDown = function ( callbackOnFinishedProcessing ) {
 		this.logger.logInfo( 'WorkerDirector received the deregister call. Terminating all workers!' );
 
-		for ( var i = 0, length = this.workerDescription.workerSupports.length; i < length; i++ ) {
+		this.instructionQueuePointer = this.instructionQueue.length;
+		this.callbackOnFinishedProcessing = Validator.verifyInput( callbackOnFinishedProcessing, null );
 
-			var supportTuple = this.workerDescription.workerSupports[ i ];
-			supportTuple.workerSupport.setTerminateRequested( true );
-			this.logger.logInfo( 'Requested termination of worker.' );
+		for ( var name in this.workerDescription.workerSupports ) {
 
-			var loaderCallbacks = supportTuple.loader.callbacks;
-			if ( Validator.isValid( loaderCallbacks.onProgress ) ) loaderCallbacks.onProgress( { detail: { text: '' } } );
+			this.workerDescription.workerSupports[ name ].terminateRequested = true;
 
 		}
-
-		this.workerDescription.workerSupports = [];
-		this.instructionQueue = [];
 	};
 
 	return WorkerDirector;

+ 19 - 9
examples/js/loaders/OBJLoader2.js

@@ -7,6 +7,8 @@
 
 if ( THREE.OBJLoader2 === undefined ) { THREE.OBJLoader2 = {} }
 
+if ( THREE.LoaderSupport === undefined ) console.error( '"THREE.LoaderSupport" is not available. "THREE.OBJLoader2" requires it. Please include "LoaderSupport.js" in your HTML.' );
+
 /**
  * Use this class to load OBJ data from files or to parse OBJ data from an arraybuffer
  * @class
@@ -16,9 +18,10 @@ if ( THREE.OBJLoader2 === undefined ) { THREE.OBJLoader2 = {} }
  */
 THREE.OBJLoader2 = (function () {
 
-	var OBJLOADER2_VERSION = '2.1.2';
+	var OBJLOADER2_VERSION = '2.2.0';
 	var LoaderBase = THREE.LoaderSupport.LoaderBase;
 	var Validator = THREE.LoaderSupport.Validator;
+	var ConsoleLogger = THREE.LoaderSupport.ConsoleLogger;
 
 	OBJLoader2.prototype = Object.create( THREE.LoaderSupport.LoaderBase.prototype );
 	OBJLoader2.prototype.constructor = OBJLoader2;
@@ -126,10 +129,6 @@ THREE.OBJLoader2 = (function () {
 			this.workerSupport = workerSupportExternal;
 			this.logger = workerSupportExternal.logger;
 
-		} else {
-
-			this.terminateWorkerOnLoad = true;
-
 		}
 		var scope = this;
 		var onMaterialsLoaded = function ( materials ) {
@@ -176,7 +175,8 @@ THREE.OBJLoader2 = (function () {
 	OBJLoader2.prototype.parse = function ( content ) {
 		this.logger.logTimeStart( 'OBJLoader2 parse: ' + this.modelName );
 
-		var parser = new Parser( this.logger );
+		var parser = new Parser();
+		parser.setLogConfig( this.logger.enabled, this.logger.debug );
 		parser.setMaterialPerSmoothingGroup( this.materialPerSmoothingGroup );
 		parser.setUseIndices( this.useIndices );
 		parser.setDisregardNormals( this.disregardNormals );
@@ -239,7 +239,6 @@ THREE.OBJLoader2 = (function () {
 					}
 				}
 			);
-			if ( scope.terminateWorkerOnLoad ) scope.workerSupport.terminateWorker();
 			scope.logger.logTimeEnd( 'OBJLoader2 parseAsync: ' + scope.modelName );
 		};
 		var scopedOnMeshLoaded = function ( payload ) {
@@ -257,6 +256,8 @@ THREE.OBJLoader2 = (function () {
 			workerCode += '/**\n';
 			workerCode += '  * This code was constructed by OBJLoader2 buildCode.\n';
 			workerCode += '  */\n\n';
+			workerCode += funcBuildObject( 'Validator', Validator );
+			workerCode += funcBuildSingelton( 'ConsoleLogger', 'ConsoleLogger', ConsoleLogger );
 			workerCode += funcBuildSingelton( 'LoaderBase', 'LoaderBase', LoaderBase );
 			workerCode += funcBuildObject( 'Consts', Consts );
 			workerCode += funcBuildSingelton( 'Parser', 'Parser', Parser );
@@ -267,6 +268,7 @@ THREE.OBJLoader2 = (function () {
 		};
 		this.workerSupport.validate( buildCode, false );
 		this.workerSupport.setCallbacks( scopedOnMeshLoaded, scopedOnLoad );
+		if ( scope.terminateWorkerOnLoad ) this.workerSupport.setTerminateRequested( true );
 
 		var materialNames = {};
 		var materials = this.builder.getMaterials();
@@ -330,7 +332,9 @@ THREE.OBJLoader2 = (function () {
 	 */
 	var Parser = (function () {
 
-		function Parser( logger ) {
+		var ConsoleLogger = THREE.LoaderSupport.ConsoleLogger;
+
+		function Parser() {
 			this.callbackProgress = null;
 			this.callbackBuilder = null;
 
@@ -348,7 +352,7 @@ THREE.OBJLoader2 = (function () {
 				faces: 0,
 				doubleIndicesCount: 0
 			};
-			this.logger = logger;
+			this.logger = new ConsoleLogger();
 			this.totalBytes = 0;
 			this.reachedFaces = false;
 		};
@@ -383,6 +387,11 @@ THREE.OBJLoader2 = (function () {
 			this.callbackProgress = callbackProgress;
 		};
 
+		Parser.prototype.setLogConfig = function ( enabled, debug ) {
+			this.logger.setEnabled( enabled );
+			this.logger.setDebug( debug );
+		};
+
 		Parser.prototype.configure = function () {
 			this.rawMesh = new RawMesh( this.materialPerSmoothingGroup, this.useIndices, this.disregardNormals );
 
@@ -1359,6 +1368,7 @@ THREE.OBJLoader2 = (function () {
 	 * @param {string} [crossOrigin] CORS value
 	 */
 	OBJLoader2.prototype._loadMtl = function ( resource, callbackOnLoad, crossOrigin ) {
+		if ( THREE.MTLLoader === undefined ) console.error( '"THREE.MTLLoader" is not available. "THREE.OBJLoader2" requires it for loading MTL files.' );
 		if ( Validator.isValid( resource ) ) this.logger.logTimeStart( 'Loading MTL: ' + resource.name );
 
 		var materials = [];

+ 13 - 1
examples/js/loaders/STLLoader.js

@@ -48,7 +48,19 @@ THREE.STLLoader.prototype = {
 		loader.setResponseType( 'arraybuffer' );
 		loader.load( url, function ( text ) {
 
-			onLoad( scope.parse( text ) );
+			try {
+
+				onLoad( scope.parse( text ) );
+
+			} catch ( exception ) {
+
+				if ( onError ) {
+
+					onError( exception );
+
+				}
+
+			}
 
 		}, onProgress, onError );
 

+ 20 - 23
examples/js/loaders/TDSLoader.js

@@ -22,8 +22,6 @@ THREE.TDSLoader = function ( manager ) {
 	this.materials = [];
 	this.meshes = [];
 
-	this.path = "";
-
 };
 
 THREE.TDSLoader.prototype = {
@@ -43,13 +41,16 @@ THREE.TDSLoader.prototype = {
 
 		var scope = this;
 
+
+		var path = this.path !== undefined ? this.path : THREE.Loader.prototype.extractUrlBase( url );
+
 		var loader = new THREE.FileLoader( this.manager );
 
 		loader.setResponseType( 'arraybuffer' );
 
 		loader.load( url, function ( data ) {
 
-			onLoad( scope.parse( data ) );
+			onLoad( scope.parse( data, path ) );
 
 		}, onProgress, onError );
 
@@ -63,14 +64,14 @@ THREE.TDSLoader.prototype = {
 	 * @param {String} path Path for external resources.
 	 * @return {Object3D} Group loaded from 3ds file.
 	 */
-	parse: function ( arraybuffer ) {
+	parse: function ( arraybuffer, path ) {
 
 		this.group = new THREE.Group();
 		this.position = 0;
 		this.materials = [];
 		this.meshes = [];
 
-		this.readFile( arraybuffer );
+		this.readFile( arraybuffer, path );
 
 		for ( var i = 0; i < this.meshes.length; i ++ ) {
 
@@ -88,7 +89,7 @@ THREE.TDSLoader.prototype = {
 	 * @method readFile
 	 * @param {ArrayBuffer} arraybuffer Arraybuffer data to be loaded.
 	 */
-	readFile: function ( arraybuffer ) {
+	readFile: function ( arraybuffer, path ) {
 
 		var data = new DataView( arraybuffer );
 		var chunk = this.readChunk( data );
@@ -107,7 +108,7 @@ THREE.TDSLoader.prototype = {
 				} else if ( next === MDATA ) {
 
 					this.resetPosition( data );
-					this.readMeshData( data );
+					this.readMeshData( data, path );
 
 				} else {
 
@@ -131,7 +132,7 @@ THREE.TDSLoader.prototype = {
 	 * @method readMeshData
 	 * @param {Dataview} data Dataview in use.
 	 */
-	readMeshData: function ( data ) {
+	readMeshData: function ( data, path ) {
 
 		var chunk = this.readChunk( data );
 		var next = this.nextChunk( data, chunk );
@@ -159,7 +160,7 @@ THREE.TDSLoader.prototype = {
 
 				this.debugMessage( 'Material' );
 				this.resetPosition( data );
-				this.readMaterialEntry( data );
+				this.readMaterialEntry( data, path );
 
 			} else {
 
@@ -215,7 +216,7 @@ THREE.TDSLoader.prototype = {
 	 * @method readMaterialEntry
 	 * @param {Dataview} data Dataview in use.
 	 */
-	readMaterialEntry: function ( data ) {
+	readMaterialEntry: function ( data, path ) {
 
 		var chunk = this.readChunk( data );
 		var next = this.nextChunk( data, chunk );
@@ -274,25 +275,25 @@ THREE.TDSLoader.prototype = {
 
 				this.debugMessage( '   ColorMap' );
 				this.resetPosition( data );
-				material.map = this.readMap( data );
+				material.map = this.readMap( data, path );
 
 			} else if ( next === MAT_BUMPMAP ) {
 
 				this.debugMessage( '   BumpMap' );
 				this.resetPosition( data );
-				material.bumpMap = this.readMap( data );
+				material.bumpMap = this.readMap( data, path );
 
 			} else if ( next === MAT_OPACMAP ) {
 
 				this.debugMessage( '   OpacityMap' );
 				this.resetPosition( data );
-				material.alphaMap = this.readMap( data );
+				material.alphaMap = this.readMap( data, path );
 
 			} else if ( next === MAT_SPECMAP ) {
 
 				this.debugMessage( '   SpecularMap' );
 				this.resetPosition( data );
-				material.specularMap = this.readMap( data );
+				material.specularMap = this.readMap( data, path );
 
 			} else {
 
@@ -561,14 +562,14 @@ THREE.TDSLoader.prototype = {
 	 * @param {Dataview} data Dataview in use.
 	 * @return {Texture} Texture read from this data chunk.
 	 */
-	readMap: function ( data ) {
+	readMap: function ( data, path ) {
 
 		var chunk = this.readChunk( data );
 		var next = this.nextChunk( data, chunk );
 		var texture = {};
 
-		var loader = new THREE.TextureLoader();
-		loader.setPath( this.path );
+		var loader = new THREE.TextureLoader( this.manager );
+		loader.setPath( path );
 
 		while ( next !== 0 ) {
 
@@ -577,7 +578,7 @@ THREE.TDSLoader.prototype = {
 				var name = this.readString( data, 128 );
 				texture = loader.load( name );
 
-				this.debugMessage( '      File: ' + this.path + name );
+				this.debugMessage( '      File: ' + path + name );
 
 			} else if ( next === MAT_MAP_UOFFSET ) {
 
@@ -898,11 +899,7 @@ THREE.TDSLoader.prototype = {
 	 */
 	setPath: function ( path ) {
 
-		if ( path !== undefined ) {
-
-			this.path = path;
-
-		}
+		this.path = path;
 
 		return this;
 

+ 9 - 4
examples/js/loaders/XLoader.js

@@ -725,6 +725,11 @@
 			key: '_makeBoneFrom_CurrentFrame',
 			value: function _makeBoneFrom_CurrentFrame() {
 
+				if ( ! this._currentFrame.FrameTransformMatrix ) {
+
+					return;
+
+				}
 				var b = new THREE.Bone();
 				b.name = this._currentFrame.name;
 				b.applyMatrix( this._currentFrame.FrameTransformMatrix );
@@ -1315,19 +1320,19 @@
 					}
 					var bufferGeometry = new THREE.BufferGeometry().fromGeometry( this._currentGeo.Geometry );
 					bufferGeometry.bones = putBones;
-					mesh = new THREE.SkinnedMesh( bufferGeometry, new THREE.MultiMaterial( this._currentGeo.Materials ) );
+					mesh = new THREE.SkinnedMesh( bufferGeometry, this._currentGeo.Materials.length === 1 ? this._currentGeo.Materials[ 0 ] : this._currentGeo.Materials );
 					mesh.skeleton.boneInverses = offsetList;
 
 				} else {
 
 					var _bufferGeometry = new THREE.BufferGeometry().fromGeometry( this._currentGeo.Geometry );
-					mesh = new THREE.Mesh( _bufferGeometry, new THREE.MultiMaterial( this._currentGeo.Materials ) );
+					mesh = new THREE.Mesh( _bufferGeometry, this._currentGeo.Materials.length === 1 ? this._currentGeo.Materials[ 0 ] : this._currentGeo.Materials );
 
 				}
 				mesh.name = this._currentGeo.name;
 				var worldBaseMx = new THREE.Matrix4();
 				var currentMxFrame = this._currentGeo.baseFrame.putBone;
-				if ( currentMxFrame.parent ) {
+				if ( currentMxFrame && currentMxFrame.parent ) {
 
 					while ( true ) {
 
@@ -1343,9 +1348,9 @@
 						}
 
 					}
+					mesh.applyMatrix( worldBaseMx );
 
 				}
-				mesh.applyMatrix( worldBaseMx );
 				this.Meshes.push( mesh );
 
 			}

+ 0 - 136
examples/js/utils/ImageUtils.js

@@ -1,136 +0,0 @@
-/**
- * @author alteredq / http://alteredqualia.com/
- * @author mrdoob / http://mrdoob.com/
- * @author Daosheng Mu / https://github.com/DaoshengMu/
- */
-
-THREE.ImageUtils = {
-
-	getNormalMap: function ( image, depth ) {
-
-		// Adapted from http://www.paulbrunt.co.uk/lab/heightnormal/
-
-		function cross( a, b ) {
-
-			return [ a[ 1 ] * b[ 2 ] - a[ 2 ] * b[ 1 ], a[ 2 ] * b[ 0 ] - a[ 0 ] * b[ 2 ], a[ 0 ] * b[ 1 ] - a[ 1 ] * b[ 0 ] ];
-
-		}
-
-		function subtract( a, b ) {
-
-			return [ a[ 0 ] - b[ 0 ], a[ 1 ] - b[ 1 ], a[ 2 ] - b[ 2 ] ];
-
-		}
-
-		function normalize( a ) {
-
-			var l = Math.sqrt( a[ 0 ] * a[ 0 ] + a[ 1 ] * a[ 1 ] + a[ 2 ] * a[ 2 ] );
-			return [ a[ 0 ] / l, a[ 1 ] / l, a[ 2 ] / l ];
-
-		}
-
-		depth = depth | 1;
-
-		var width = image.width;
-		var height = image.height;
-
-		var canvas = document.createElement( 'canvas' );
-		canvas.width = width;
-		canvas.height = height;
-
-		var context = canvas.getContext( '2d' );
-		context.drawImage( image, 0, 0 );
-
-		var data = context.getImageData( 0, 0, width, height ).data;
-		var imageData = context.createImageData( width, height );
-		var output = imageData.data;
-
-		for ( var x = 0; x < width; x ++ ) {
-
-			for ( var y = 0; y < height; y ++ ) {
-
-				var ly = y - 1 < 0 ? 0 : y - 1;
-				var uy = y + 1 > height - 1 ? height - 1 : y + 1;
-				var lx = x - 1 < 0 ? 0 : x - 1;
-				var ux = x + 1 > width - 1 ? width - 1 : x + 1;
-
-				var points = [];
-				var origin = [ 0, 0, data[ ( y * width + x ) * 4 ] / 255 * depth ];
-				points.push( [ - 1, 0, data[ ( y * width + lx ) * 4 ] / 255 * depth ] );
-				points.push( [ - 1, - 1, data[ ( ly * width + lx ) * 4 ] / 255 * depth ] );
-				points.push( [ 0, - 1, data[ ( ly * width + x ) * 4 ] / 255 * depth ] );
-				points.push( [ 1, - 1, data[ ( ly * width + ux ) * 4 ] / 255 * depth ] );
-				points.push( [ 1, 0, data[ ( y * width + ux ) * 4 ] / 255 * depth ] );
-				points.push( [ 1, 1, data[ ( uy * width + ux ) * 4 ] / 255 * depth ] );
-				points.push( [ 0, 1, data[ ( uy * width + x ) * 4 ] / 255 * depth ] );
-				points.push( [ - 1, 1, data[ ( uy * width + lx ) * 4 ] / 255 * depth ] );
-
-				var normals = [];
-				var num_points = points.length;
-
-				for ( var i = 0; i < num_points; i ++ ) {
-
-					var v1 = points[ i ];
-					var v2 = points[ ( i + 1 ) % num_points ];
-					v1 = subtract( v1, origin );
-					v2 = subtract( v2, origin );
-					normals.push( normalize( cross( v1, v2 ) ) );
-
-				}
-
-				var normal = [ 0, 0, 0 ];
-
-				for ( var i = 0; i < normals.length; i ++ ) {
-
-					normal[ 0 ] += normals[ i ][ 0 ];
-					normal[ 1 ] += normals[ i ][ 1 ];
-					normal[ 2 ] += normals[ i ][ 2 ];
-
-				}
-
-				normal[ 0 ] /= normals.length;
-				normal[ 1 ] /= normals.length;
-				normal[ 2 ] /= normals.length;
-
-				var idx = ( y * width + x ) * 4;
-
-				output[ idx ] = ( ( normal[ 0 ] + 1.0 ) / 2.0 * 255 ) | 0;
-				output[ idx + 1 ] = ( ( normal[ 1 ] + 1.0 ) / 2.0 * 255 ) | 0;
-				output[ idx + 2 ] = ( normal[ 2 ] * 255 ) | 0;
-				output[ idx + 3 ] = 255;
-
-			}
-
-		}
-
-		context.putImageData( imageData, 0, 0 );
-
-		return canvas;
-
-	},
-
-	generateDataTexture: function ( width, height, color ) {
-
-		var size = width * height;
-		var data = new Uint8Array( 3 * size );
-
-		var r = Math.floor( color.r * 255 );
-		var g = Math.floor( color.g * 255 );
-		var b = Math.floor( color.b * 255 );
-
-		for ( var i = 0; i < size; i ++ ) {
-
-			data[ i * 3 ] 	   = r;
-			data[ i * 3 + 1 ] = g;
-			data[ i * 3 + 2 ] = b;
-
-		}
-
-		var texture = new THREE.DataTexture( data, width, height, THREE.RGBFormat );
-		texture.needsUpdate = true;
-
-		return texture;
-
-	}
-
-};

+ 0 - 2
examples/js/vr/ViveController.js

@@ -40,7 +40,6 @@ THREE.ViveController = function ( id ) {
 	}
 
 	this.matrixAutoUpdate = false;
-	this.standingMatrix = new THREE.Matrix4();
 
 	this.getGamepad = function () {
 
@@ -72,7 +71,6 @@ THREE.ViveController = function ( id ) {
 			if ( pose.position !== null ) scope.position.fromArray( pose.position );
 			if ( pose.orientation !== null ) scope.quaternion.fromArray( pose.orientation );
 			scope.matrix.compose( scope.position, scope.quaternion, scope.scale );
-			scope.matrix.multiplyMatrices( scope.standingMatrix, scope.matrix );
 			scope.matrixWorldNeedsUpdate = true;
 			scope.visible = true;
 

+ 0 - 617
examples/webgl_geometry_text_earcut.html

@@ -1,617 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-	<head>
-		<title>three.js webgl - geometry - text</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 {
-				font-family: Monospace;
-				background-color: #000;
-				color: #fff;
-				margin: 0px;
-				overflow: hidden;
-			}
-			#info {
-				position: absolute;
-				top: 10px;
-				width: 100%;
-				text-align: center;
-				z-index: 100;
-				display:block;
-			}
-			#info a, .button { color: #f00; font-weight: bold; text-decoration: underline; cursor: pointer }
-		</style>
-	</head>
-	<body>
-
-		<div id="info">
-		<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - procedural 3D text by <a href="http://www.lab4games.net/zz85/blog" target="_blank" rel="noopener">zz85</a> &amp; alteredq
-		<br/>built-in shape triangulation has been replaced with <a href="https://github.com/mapbox/earcut">Earcut</a> by <a href="https://github.com/mourner" target="_blank" rel="noopener">mourner</a>
-		<br/>type to enter new text, drag to spin the text
-		<br/><span class="button" id="color">change color</span>,
-			<span class="button" id="font">change font</span>,
-			<span class="button" id="weight">change weight</span>,
-			<span class="button" id="bevel">change bevel</span>
-			<a id="permalink" href="#">permalink</a>
-		</div>
-
-
-		<script src="../build/three.js"></script>
-		<script src="js/utils/GeometryUtils.js"></script>
-
-		<script src="js/Detector.js"></script>
-		<script src="js/libs/stats.min.js"></script>
-
-
-		<!-- replace built-in triangulation with Earcut -->
-		<script src="js/libs/earcut.js"></script>
-		<script>
-			THREE.ShapeUtils.triangulateShape = function ( contour, holes ) {
-
-				function removeDupEndPts( points ) {
-
-					var l = points.length;
-					if ( l > 2 && points[ l - 1 ].equals( points[ 0 ] ) ) {
-
-						points.pop();
-
-					}
-
-				}
-
-				function addContour( vertices, contour ) {
-
-					for ( var i = 0; i < contour.length; i ++ ) {
-
-						vertices.push( contour[ i ].x );
-						vertices.push( contour[ i ].y );
-
-					}
-
-				}
-
-				removeDupEndPts( contour );
-				holes.forEach( removeDupEndPts );
-
-				var vertices = [];
-				addContour( vertices, contour );
-				var holeIndices = [];
-				var holeIndex = contour.length;
-				for ( i = 0; i < holes.length; i ++ ) {
-
-					holeIndices.push( holeIndex );
-					holeIndex += holes[ i ].length;
-					addContour( vertices, holes[ i ] );
-
-				}
-
-				var result = earcut( vertices, holeIndices, 2 );
-				var grouped = [];
-				for ( var i = 0; i < result.length; i += 3 ) {
-
-					grouped.push( result.slice( i, i + 3 ) );
-
-				}
-
-				return grouped;
-
-			};
-
-		</script>
-
-		<script>
-
-			if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
-
-			THREE.Cache.enabled = true;
-
-			var container, stats, permalink, hex, color;
-
-			var camera, cameraTarget, scene, renderer;
-
-			var group, textMesh1, textMesh2, textGeo, materials;
-
-			var firstLetter = true;
-
-			var text = "Earcut",
-
-				height = 20,
-				size = 70,
-				hover = 30,
-
-				curveSegments = 4,
-
-				bevelThickness = 2,
-				bevelSize = 1.5,
-				bevelSegments = 3,
-				bevelEnabled = true,
-
-				font = undefined,
-
-				fontName = "optimer", // helvetiker, optimer, gentilis, droid sans, droid serif
-				fontWeight = "bold"; // normal bold
-
-			var mirror = true;
-
-			var fontMap = {
-
-				"helvetiker": 0,
-				"optimer": 1,
-				"gentilis": 2,
-				"droid/droid_sans": 3,
-				"droid/droid_serif": 4
-
-			};
-
-			var weightMap = {
-
-				"regular": 0,
-				"bold": 1
-
-			};
-
-			var reverseFontMap = [];
-			var reverseWeightMap = [];
-
-			for ( var i in fontMap ) reverseFontMap[ fontMap[i] ] = i;
-			for ( var i in weightMap ) reverseWeightMap[ weightMap[i] ] = i;
-
-			var targetRotation = 0;
-			var targetRotationOnMouseDown = 0;
-
-			var mouseX = 0;
-			var mouseXOnMouseDown = 0;
-
-			var windowHalfX = window.innerWidth / 2;
-			var windowHalfY = window.innerHeight / 2;
-
-			var fontIndex = 1;
-
-			init();
-			animate();
-
-			function decimalToHex( d ) {
-
-				var hex = Number( d ).toString( 16 );
-				hex = "000000".substr( 0, 6 - hex.length ) + hex;
-				return hex.toUpperCase();
-
-			}
-
-			function init() {
-
-				container = document.createElement( 'div' );
-				document.body.appendChild( container );
-
-				permalink = document.getElementById( "permalink" );
-
-				// CAMERA
-
-				camera = new THREE.PerspectiveCamera( 30, window.innerWidth / window.innerHeight, 1, 1500 );
-				camera.position.set( 0, 400, 700 );
-
-				cameraTarget = new THREE.Vector3( 0, 150, 0 );
-
-				// SCENE
-
-				scene = new THREE.Scene();
-				scene.background = new THREE.Color( 0x000000 );
-				scene.fog = new THREE.Fog( 0x000000, 250, 1400 );
-
-				// LIGHTS
-
-				var dirLight = new THREE.DirectionalLight( 0xffffff, 0.125 );
-				dirLight.position.set( 0, 0, 1 ).normalize();
-				scene.add( dirLight );
-
-				var pointLight = new THREE.PointLight( 0xffffff, 1.5 );
-				pointLight.position.set( 0, 100, 90 );
-				scene.add( pointLight );
-
-				// Get text from hash
-
-				var hash = document.location.hash.substr( 1 );
-
-				if ( hash.length !== 0 ) {
-
-					var colorhash  = hash.substring( 0, 6 );
-					var fonthash   = hash.substring( 6, 7 );
-					var weighthash = hash.substring( 7, 8 );
-					var bevelhash  = hash.substring( 8, 9 );
-					var texthash   = hash.substring( 10 );
-
-					hex = colorhash;
-					pointLight.color.setHex( parseInt( colorhash, 16 ) );
-
-					fontName = reverseFontMap[ parseInt( fonthash ) ];
-					fontWeight = reverseWeightMap[ parseInt( weighthash ) ];
-
-					bevelEnabled = parseInt( bevelhash );
-
-					text = decodeURI( texthash );
-
-					updatePermalink();
-
-				} else {
-
-					pointLight.color.setHSL( Math.random(), 1, 0.5 );
-					hex = decimalToHex( pointLight.color.getHex() );
-
-				}
-
-				materials = [
-					new THREE.MeshPhongMaterial( { color: 0xffffff, flatShading: true } ), // front
-					new THREE.MeshPhongMaterial( { color: 0xffffff } ) // side
-				];
-
-				group = new THREE.Group();
-				group.position.y = 100;
-
-				scene.add( group );
-
-				loadFont();
-
-				var plane = new THREE.Mesh(
-					new THREE.PlaneBufferGeometry( 10000, 10000 ),
-					new THREE.MeshBasicMaterial( { color: 0xffffff, opacity: 0.5, transparent: true } )
-				);
-				plane.position.y = 100;
-				plane.rotation.x = - Math.PI / 2;
-				scene.add( plane );
-
-				// RENDERER
-
-				renderer = new THREE.WebGLRenderer( { antialias: true } );
-				renderer.setPixelRatio( window.devicePixelRatio );
-				renderer.setSize( window.innerWidth, window.innerHeight );
-				container.appendChild( renderer.domElement );
-
-				// STATS
-
-				stats = new Stats();
-				//container.appendChild( stats.dom );
-
-				// EVENTS
-
-				document.addEventListener( 'mousedown', onDocumentMouseDown, false );
-				document.addEventListener( 'touchstart', onDocumentTouchStart, false );
-				document.addEventListener( 'touchmove', onDocumentTouchMove, false );
-				document.addEventListener( 'keypress', onDocumentKeyPress, false );
-				document.addEventListener( 'keydown', onDocumentKeyDown, false );
-
-				document.getElementById( "color" ).addEventListener( 'click', function() {
-
-					pointLight.color.setHSL( Math.random(), 1, 0.5 );
-					hex = decimalToHex( pointLight.color.getHex() );
-
-					updatePermalink();
-
-				}, false );
-
-				document.getElementById( "font" ).addEventListener( 'click', function() {
-
-					fontIndex ++;
-
-					fontName = reverseFontMap[ fontIndex % reverseFontMap.length ];
-
-					loadFont();
-
-				}, false );
-
-
-				document.getElementById( "weight" ).addEventListener( 'click', function() {
-
-					if ( fontWeight === "bold" ) {
-
-						fontWeight = "regular";
-
-					} else {
-
-						fontWeight = "bold";
-
-					}
-
-					loadFont();
-
-				}, false );
-
-				document.getElementById( "bevel" ).addEventListener( 'click', function() {
-
-					bevelEnabled = !bevelEnabled;
-
-					refreshText();
-
-				}, false );
-
-				//
-
-				window.addEventListener( 'resize', onWindowResize, false );
-
-			}
-
-			function onWindowResize() {
-
-				windowHalfX = window.innerWidth / 2;
-				windowHalfY = window.innerHeight / 2;
-
-				camera.aspect = window.innerWidth / window.innerHeight;
-				camera.updateProjectionMatrix();
-
-				renderer.setSize( window.innerWidth, window.innerHeight );
-
-			}
-
-			//
-
-			function boolToNum( b ) {
-
-				return b ? 1 : 0;
-
-			}
-
-			function updatePermalink() {
-
-				var link = hex + fontMap[ fontName ] + weightMap[ fontWeight ] + boolToNum( bevelEnabled ) + "#" + encodeURI( text );
-
-				permalink.href = "#" + link;
-				window.location.hash = link;
-
-			}
-
-			function onDocumentKeyDown( event ) {
-
-				if ( firstLetter ) {
-
-					firstLetter = false;
-					text = "";
-
-				}
-
-				var keyCode = event.keyCode;
-
-				// backspace
-
-				if ( keyCode == 8 ) {
-
-					event.preventDefault();
-
-					text = text.substring( 0, text.length - 1 );
-					refreshText();
-
-					return false;
-
-				}
-
-			}
-
-			function onDocumentKeyPress( event ) {
-
-				var keyCode = event.which;
-
-				// backspace
-
-				if ( keyCode == 8 ) {
-
-					event.preventDefault();
-
-				} else {
-
-					var ch = String.fromCharCode( keyCode );
-					text += ch;
-
-					refreshText();
-
-				}
-
-			}
-
-			function loadFont() {
-
-				var loader = new THREE.FontLoader();
-				loader.load( 'fonts/' + fontName + '_' + fontWeight + '.typeface.json', function ( response ) {
-
-					font = response;
-
-					refreshText();
-
-				} );
-
-			}
-
-			function createText() {
-
-				textGeo = new THREE.TextGeometry( text, {
-
-					font: font,
-
-					size: size,
-					height: height,
-					curveSegments: curveSegments,
-
-					bevelThickness: bevelThickness,
-					bevelSize: bevelSize,
-					bevelEnabled: bevelEnabled,
-
-					material: 0,
-					extrudeMaterial: 1
-
-				});
-
-				textGeo.computeBoundingBox();
-				textGeo.computeVertexNormals();
-
-				// "fix" side normals by removing z-component of normals for side faces
-				// (this doesn't work well for beveled geometry as then we lose nice curvature around z-axis)
-
-				if ( ! bevelEnabled ) {
-
-					var triangleAreaHeuristics = 0.1 * ( height * size );
-
-					for ( var i = 0; i < textGeo.faces.length; i ++ ) {
-
-						var face = textGeo.faces[ i ];
-
-						if ( face.materialIndex == 1 ) {
-
-							for ( var j = 0; j < face.vertexNormals.length; j ++ ) {
-
-								face.vertexNormals[ j ].z = 0;
-								face.vertexNormals[ j ].normalize();
-
-							}
-
-							var va = textGeo.vertices[ face.a ];
-							var vb = textGeo.vertices[ face.b ];
-							var vc = textGeo.vertices[ face.c ];
-
-							var s = THREE.GeometryUtils.triangleArea( va, vb, vc );
-
-							if ( s > triangleAreaHeuristics ) {
-
-								for ( var j = 0; j < face.vertexNormals.length; j ++ ) {
-
-									face.vertexNormals[ j ].copy( face.normal );
-
-								}
-
-							}
-
-						}
-
-					}
-
-				}
-
-				var centerOffset = -0.5 * ( textGeo.boundingBox.max.x - textGeo.boundingBox.min.x );
-
-				textMesh1 = new THREE.Mesh( textGeo, materials );
-
-				textMesh1.position.x = centerOffset;
-				textMesh1.position.y = hover;
-				textMesh1.position.z = 0;
-
-				textMesh1.rotation.x = 0;
-				textMesh1.rotation.y = Math.PI * 2;
-
-				group.add( textMesh1 );
-
-				if ( mirror ) {
-
-					textMesh2 = new THREE.Mesh( textGeo, materials );
-
-					textMesh2.position.x = centerOffset;
-					textMesh2.position.y = -hover;
-					textMesh2.position.z = height;
-
-					textMesh2.rotation.x = Math.PI;
-					textMesh2.rotation.y = Math.PI * 2;
-
-					group.add( textMesh2 );
-
-				}
-
-			}
-
-			function refreshText() {
-
-				updatePermalink();
-
-				group.remove( textMesh1 );
-				if ( mirror ) group.remove( textMesh2 );
-
-				if ( !text ) return;
-
-				createText();
-
-			}
-
-			function onDocumentMouseDown( event ) {
-
-				event.preventDefault();
-
-				document.addEventListener( 'mousemove', onDocumentMouseMove, false );
-				document.addEventListener( 'mouseup', onDocumentMouseUp, false );
-				document.addEventListener( 'mouseout', onDocumentMouseOut, false );
-
-				mouseXOnMouseDown = event.clientX - windowHalfX;
-				targetRotationOnMouseDown = targetRotation;
-
-			}
-
-			function onDocumentMouseMove( event ) {
-
-				mouseX = event.clientX - windowHalfX;
-
-				targetRotation = targetRotationOnMouseDown + ( mouseX - mouseXOnMouseDown ) * 0.02;
-
-			}
-
-			function onDocumentMouseUp( event ) {
-
-				document.removeEventListener( 'mousemove', onDocumentMouseMove, false );
-				document.removeEventListener( 'mouseup', onDocumentMouseUp, false );
-				document.removeEventListener( 'mouseout', onDocumentMouseOut, false );
-
-			}
-
-			function onDocumentMouseOut( event ) {
-
-				document.removeEventListener( 'mousemove', onDocumentMouseMove, false );
-				document.removeEventListener( 'mouseup', onDocumentMouseUp, false );
-				document.removeEventListener( 'mouseout', onDocumentMouseOut, false );
-
-			}
-
-			function onDocumentTouchStart( event ) {
-
-				if ( event.touches.length == 1 ) {
-
-					event.preventDefault();
-
-					mouseXOnMouseDown = event.touches[ 0 ].pageX - windowHalfX;
-					targetRotationOnMouseDown = targetRotation;
-
-				}
-
-			}
-
-			function onDocumentTouchMove( event ) {
-
-				if ( event.touches.length == 1 ) {
-
-					event.preventDefault();
-
-					mouseX = event.touches[ 0 ].pageX - windowHalfX;
-					targetRotation = targetRotationOnMouseDown + ( mouseX - mouseXOnMouseDown ) * 0.05;
-
-				}
-
-			}
-
-			//
-
-			function animate() {
-
-				requestAnimationFrame( animate );
-
-				render();
-				stats.update();
-
-			}
-
-			function render() {
-
-				group.rotation.y += ( targetRotation - group.rotation.y ) * 0.05;
-
-				camera.lookAt( cameraTarget );
-
-				renderer.clear();
-				renderer.render( scene, camera );
-
-			}
-
-		</script>
-
-	</body>
-</html>

+ 0 - 589
examples/webgl_geometry_text_pnltri.html

@@ -1,589 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-	<head>
-		<title>three.js webgl - geometry - text</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 {
-				font-family: Monospace;
-				background-color: #000;
-				color: #fff;
-				margin: 0px;
-				overflow: hidden;
-			}
-			#info {
-				position: absolute;
-				top: 10px;
-				width: 100%;
-				text-align: center;
-				z-index: 100;
-				display:block;
-			}
-			#info a, .button { color: #f00; font-weight: bold; text-decoration: underline; cursor: pointer }
-		</style>
-	</head>
-	<body>
-
-		<div id="info">
-		<a href="http://threejs.org" target="_blank" rel="noopener">three.js</a> - procedural 3D text by <a href="http://www.lab4games.net/zz85/blog" target="_blank" rel="noopener">zz85</a> &amp; alteredq
-		<br/>built-in shape triangulation has been replaced with <a href="https://github.com/jahting/pnltri.js">PnlTri.js</a> by <a href="https://github.com/jahting" target="_blank" rel="noopener">j ahting</a>
-		<br/>type to enter new text, drag to spin the text
-		<br/><span class="button" id="color">change color</span>,
-			<span class="button" id="font">change font</span>,
-			<span class="button" id="weight">change weight</span>,
-			<span class="button" id="bevel">change bevel</span>
-			<a id="permalink" href="#">permalink</a>
-		</div>
-
-
-		<script src="../build/three.js"></script>
-		<script src="js/utils/GeometryUtils.js"></script>
-
-		<script src="js/Detector.js"></script>
-		<script src="js/libs/stats.min.js"></script>
-
-
-		<!-- replace built-in triangulation with PnlTri.js -->
-		<script src="js/libs/pnltri.min.js"></script>
-		<script>
-			THREE.ShapeUtils.triangulateShape = ( function () {
-				var pnlTriangulator = new PNLTRI.Triangulator();
-				function removeDupEndPts(points) {
-
-					var l = points.length;
-
-					if ( l > 2 && points[ l - 1 ].equals( points[ 0 ] ) ) {
-
-						points.pop();
-
-					}
-
-				}
-
-				return function triangulateShape( contour, holes ) {
-					// console.log("new Triangulation: PnlTri.js " + PNLTRI.REVISION );
-
-					removeDupEndPts( contour );
-					holes.forEach( removeDupEndPts );
-
-					return pnlTriangulator.triangulate_polygon( [ contour ].concat(holes) );
-				};
-			} )();
-		</script>
-
-		<script>
-
-			if ( ! Detector.webgl ) Detector.addGetWebGLMessage();
-
-			THREE.Cache.enabled = true;
-
-			var container, stats, permalink, hex, color;
-
-			var camera, cameraTarget, scene, renderer;
-
-			var group, textMesh1, textMesh2, textGeo, materials;
-
-			var firstLetter = true;
-
-			var text = "PnlTri",
-
-				height = 20,
-				size = 70,
-				hover = 30,
-
-				curveSegments = 4,
-
-				bevelThickness = 2,
-				bevelSize = 1.5,
-				bevelSegments = 3,
-				bevelEnabled = true,
-
-				font = undefined,
-
-				fontName = "optimer", // helvetiker, optimer, gentilis, droid sans, droid serif
-				fontWeight = "bold"; // normal bold
-
-			var mirror = true;
-
-			var fontMap = {
-
-				"helvetiker": 0,
-				"optimer": 1,
-				"gentilis": 2,
-				"droid/droid_sans": 3,
-				"droid/droid_serif": 4
-
-			};
-
-			var weightMap = {
-
-				"regular": 0,
-				"bold": 1
-
-			};
-
-			var reverseFontMap = [];
-			var reverseWeightMap = [];
-
-			for ( var i in fontMap ) reverseFontMap[ fontMap[i] ] = i;
-			for ( var i in weightMap ) reverseWeightMap[ weightMap[i] ] = i;
-
-			var targetRotation = 0;
-			var targetRotationOnMouseDown = 0;
-
-			var mouseX = 0;
-			var mouseXOnMouseDown = 0;
-
-			var windowHalfX = window.innerWidth / 2;
-			var windowHalfY = window.innerHeight / 2;
-
-			var fontIndex = 1;
-
-			init();
-			animate();
-
-			function decimalToHex( d ) {
-
-				var hex = Number( d ).toString( 16 );
-				hex = "000000".substr( 0, 6 - hex.length ) + hex;
-				return hex.toUpperCase();
-
-			}
-
-			function init() {
-
-				container = document.createElement( 'div' );
-				document.body.appendChild( container );
-
-				permalink = document.getElementById( "permalink" );
-
-				// CAMERA
-
-				camera = new THREE.PerspectiveCamera( 30, window.innerWidth / window.innerHeight, 1, 1500 );
-				camera.position.set( 0, 400, 700 );
-
-				cameraTarget = new THREE.Vector3( 0, 150, 0 );
-
-				// SCENE
-
-				scene = new THREE.Scene();
-				scene.background = new THREE.Color( 0x000000 );
-				scene.fog = new THREE.Fog( 0x000000, 250, 1400 );
-
-				// LIGHTS
-
-				var dirLight = new THREE.DirectionalLight( 0xffffff, 0.125 );
-				dirLight.position.set( 0, 0, 1 ).normalize();
-				scene.add( dirLight );
-
-				var pointLight = new THREE.PointLight( 0xffffff, 1.5 );
-				pointLight.position.set( 0, 100, 90 );
-				scene.add( pointLight );
-
-				// Get text from hash
-
-				var hash = document.location.hash.substr( 1 );
-
-				if ( hash.length !== 0 ) {
-
-					var colorhash  = hash.substring( 0, 6 );
-					var fonthash   = hash.substring( 6, 7 );
-					var weighthash = hash.substring( 7, 8 );
-					var bevelhash  = hash.substring( 8, 9 );
-					var texthash   = hash.substring( 10 );
-
-					hex = colorhash;
-					pointLight.color.setHex( parseInt( colorhash, 16 ) );
-
-					fontName = reverseFontMap[ parseInt( fonthash ) ];
-					fontWeight = reverseWeightMap[ parseInt( weighthash ) ];
-
-					bevelEnabled = parseInt( bevelhash );
-
-					text = decodeURI( texthash );
-
-					updatePermalink();
-
-				} else {
-
-					pointLight.color.setHSL( Math.random(), 1, 0.5 );
-					hex = decimalToHex( pointLight.color.getHex() );
-
-				}
-
-				materials = [
-					new THREE.MeshPhongMaterial( { color: 0xffffff, flatShading: true } ), // front
-					new THREE.MeshPhongMaterial( { color: 0xffffff } ) // side
-				];
-
-				group = new THREE.Group();
-				group.position.y = 100;
-
-				scene.add( group );
-
-				loadFont();
-
-				var plane = new THREE.Mesh(
-					new THREE.PlaneBufferGeometry( 10000, 10000 ),
-					new THREE.MeshBasicMaterial( { color: 0xffffff, opacity: 0.5, transparent: true } )
-				);
-				plane.position.y = 100;
-				plane.rotation.x = - Math.PI / 2;
-				scene.add( plane );
-
-				// RENDERER
-
-				renderer = new THREE.WebGLRenderer( { antialias: true } );
-				renderer.setPixelRatio( window.devicePixelRatio );
-				renderer.setSize( window.innerWidth, window.innerHeight );
-				container.appendChild( renderer.domElement );
-
-				// STATS
-
-				stats = new Stats();
-				//container.appendChild( stats.dom );
-
-				// EVENTS
-
-				document.addEventListener( 'mousedown', onDocumentMouseDown, false );
-				document.addEventListener( 'touchstart', onDocumentTouchStart, false );
-				document.addEventListener( 'touchmove', onDocumentTouchMove, false );
-				document.addEventListener( 'keypress', onDocumentKeyPress, false );
-				document.addEventListener( 'keydown', onDocumentKeyDown, false );
-
-				document.getElementById( "color" ).addEventListener( 'click', function() {
-
-					pointLight.color.setHSL( Math.random(), 1, 0.5 );
-					hex = decimalToHex( pointLight.color.getHex() );
-
-					updatePermalink();
-
-				}, false );
-
-				document.getElementById( "font" ).addEventListener( 'click', function() {
-
-					fontIndex ++;
-
-					fontName = reverseFontMap[ fontIndex % reverseFontMap.length ];
-
-					loadFont();
-
-				}, false );
-
-
-				document.getElementById( "weight" ).addEventListener( 'click', function() {
-
-					if ( fontWeight === "bold" ) {
-
-						fontWeight = "regular";
-
-					} else {
-
-						fontWeight = "bold";
-
-					}
-
-					loadFont();
-
-				}, false );
-
-				document.getElementById( "bevel" ).addEventListener( 'click', function() {
-
-					bevelEnabled = !bevelEnabled;
-
-					refreshText();
-
-				}, false );
-
-				//
-
-				window.addEventListener( 'resize', onWindowResize, false );
-
-			}
-
-			function onWindowResize() {
-
-				windowHalfX = window.innerWidth / 2;
-				windowHalfY = window.innerHeight / 2;
-
-				camera.aspect = window.innerWidth / window.innerHeight;
-				camera.updateProjectionMatrix();
-
-				renderer.setSize( window.innerWidth, window.innerHeight );
-
-			}
-
-			//
-
-			function boolToNum( b ) {
-
-				return b ? 1 : 0;
-
-			}
-
-			function updatePermalink() {
-
-				var link = hex + fontMap[ fontName ] + weightMap[ fontWeight ] + boolToNum( bevelEnabled ) + "#" + encodeURI( text );
-
-				permalink.href = "#" + link;
-				window.location.hash = link;
-
-			}
-
-			function onDocumentKeyDown( event ) {
-
-				if ( firstLetter ) {
-
-					firstLetter = false;
-					text = "";
-
-				}
-
-				var keyCode = event.keyCode;
-
-				// backspace
-
-				if ( keyCode == 8 ) {
-
-					event.preventDefault();
-
-					text = text.substring( 0, text.length - 1 );
-					refreshText();
-
-					return false;
-
-				}
-
-			}
-
-			function onDocumentKeyPress( event ) {
-
-				var keyCode = event.which;
-
-				// backspace
-
-				if ( keyCode == 8 ) {
-
-					event.preventDefault();
-
-				} else {
-
-					var ch = String.fromCharCode( keyCode );
-					text += ch;
-
-					refreshText();
-
-				}
-
-			}
-
-			function loadFont() {
-
-				var loader = new THREE.FontLoader();
-				loader.load( 'fonts/' + fontName + '_' + fontWeight + '.typeface.json', function ( response ) {
-
-					font = response;
-
-					refreshText();
-
-				} );
-
-			}
-
-			function createText() {
-
-				textGeo = new THREE.TextGeometry( text, {
-
-					font: font,
-
-					size: size,
-					height: height,
-					curveSegments: curveSegments,
-
-					bevelThickness: bevelThickness,
-					bevelSize: bevelSize,
-					bevelEnabled: bevelEnabled,
-
-					material: 0,
-					extrudeMaterial: 1
-
-				});
-
-				textGeo.computeBoundingBox();
-				textGeo.computeVertexNormals();
-
-				// "fix" side normals by removing z-component of normals for side faces
-				// (this doesn't work well for beveled geometry as then we lose nice curvature around z-axis)
-
-				if ( ! bevelEnabled ) {
-
-					var triangleAreaHeuristics = 0.1 * ( height * size );
-
-					for ( var i = 0; i < textGeo.faces.length; i ++ ) {
-
-						var face = textGeo.faces[ i ];
-
-						if ( face.materialIndex == 1 ) {
-
-							for ( var j = 0; j < face.vertexNormals.length; j ++ ) {
-
-								face.vertexNormals[ j ].z = 0;
-								face.vertexNormals[ j ].normalize();
-
-							}
-
-							var va = textGeo.vertices[ face.a ];
-							var vb = textGeo.vertices[ face.b ];
-							var vc = textGeo.vertices[ face.c ];
-
-							var s = THREE.GeometryUtils.triangleArea( va, vb, vc );
-
-							if ( s > triangleAreaHeuristics ) {
-
-								for ( var j = 0; j < face.vertexNormals.length; j ++ ) {
-
-									face.vertexNormals[ j ].copy( face.normal );
-
-								}
-
-							}
-
-						}
-
-					}
-
-				}
-
-				var centerOffset = -0.5 * ( textGeo.boundingBox.max.x - textGeo.boundingBox.min.x );
-
-				textMesh1 = new THREE.Mesh( textGeo, materials );
-
-				textMesh1.position.x = centerOffset;
-				textMesh1.position.y = hover;
-				textMesh1.position.z = 0;
-
-				textMesh1.rotation.x = 0;
-				textMesh1.rotation.y = Math.PI * 2;
-
-				group.add( textMesh1 );
-
-				if ( mirror ) {
-
-					textMesh2 = new THREE.Mesh( textGeo, materials );
-
-					textMesh2.position.x = centerOffset;
-					textMesh2.position.y = -hover;
-					textMesh2.position.z = height;
-
-					textMesh2.rotation.x = Math.PI;
-					textMesh2.rotation.y = Math.PI * 2;
-
-					group.add( textMesh2 );
-
-				}
-
-			}
-
-			function refreshText() {
-
-				updatePermalink();
-
-				group.remove( textMesh1 );
-				if ( mirror ) group.remove( textMesh2 );
-
-				if ( !text ) return;
-
-				createText();
-
-			}
-
-			function onDocumentMouseDown( event ) {
-
-				event.preventDefault();
-
-				document.addEventListener( 'mousemove', onDocumentMouseMove, false );
-				document.addEventListener( 'mouseup', onDocumentMouseUp, false );
-				document.addEventListener( 'mouseout', onDocumentMouseOut, false );
-
-				mouseXOnMouseDown = event.clientX - windowHalfX;
-				targetRotationOnMouseDown = targetRotation;
-
-			}
-
-			function onDocumentMouseMove( event ) {
-
-				mouseX = event.clientX - windowHalfX;
-
-				targetRotation = targetRotationOnMouseDown + ( mouseX - mouseXOnMouseDown ) * 0.02;
-
-			}
-
-			function onDocumentMouseUp( event ) {
-
-				document.removeEventListener( 'mousemove', onDocumentMouseMove, false );
-				document.removeEventListener( 'mouseup', onDocumentMouseUp, false );
-				document.removeEventListener( 'mouseout', onDocumentMouseOut, false );
-
-			}
-
-			function onDocumentMouseOut( event ) {
-
-				document.removeEventListener( 'mousemove', onDocumentMouseMove, false );
-				document.removeEventListener( 'mouseup', onDocumentMouseUp, false );
-				document.removeEventListener( 'mouseout', onDocumentMouseOut, false );
-
-			}
-
-			function onDocumentTouchStart( event ) {
-
-				if ( event.touches.length == 1 ) {
-
-					event.preventDefault();
-
-					mouseXOnMouseDown = event.touches[ 0 ].pageX - windowHalfX;
-					targetRotationOnMouseDown = targetRotation;
-
-				}
-
-			}
-
-			function onDocumentTouchMove( event ) {
-
-				if ( event.touches.length == 1 ) {
-
-					event.preventDefault();
-
-					mouseX = event.touches[ 0 ].pageX - windowHalfX;
-					targetRotation = targetRotationOnMouseDown + ( mouseX - mouseXOnMouseDown ) * 0.05;
-
-				}
-
-			}
-
-			//
-
-			function animate() {
-
-				requestAnimationFrame( animate );
-
-				render();
-				stats.update();
-
-			}
-
-			function render() {
-
-				group.rotation.y += ( targetRotation - group.rotation.y ) * 0.05;
-
-				camera.lookAt( cameraTarget );
-
-				renderer.clear();
-				renderer.render( scene, camera );
-
-			}
-
-		</script>
-
-	</body>
-</html>

+ 3 - 2
examples/webgl_interactive_raycasting_points.html

@@ -217,8 +217,9 @@
 				clock = new THREE.Clock();
 
 				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 10000 );
-				camera.applyMatrix( new THREE.Matrix4().makeTranslation( 0,0,20 ) );
-				camera.applyMatrix( new THREE.Matrix4().makeRotationX( -0.5 ) );
+				camera.position.set( 10, 10, 10 );
+				camera.lookAt( scene.position );
+				camera.updateMatrix();
 
 				//
 

+ 19 - 10
examples/webgl_loader_obj2_meshspray.html

@@ -142,12 +142,14 @@
 						workerCode += '/**\n';
 						workerCode += '  * This code was constructed by MeshSpray buildCode.\n';
 						workerCode += '  */\n\n';
+						workerCode += funcBuildObject( 'Validator', Validator );
+						workerCode += funcBuildSingelton( 'ConsoleLogger', 'ConsoleLogger', ConsoleLogger );
 						workerCode += funcBuildSingelton( 'Parser', 'Parser', Parser );
 
 						return workerCode;
 					};
 					var libs2Load = [ 'build/three.min.js' ];
-					this.workerSupport.validate( buildCode, false, libs2Load, '../' );
+					this.workerSupport.validate( buildCode, libs2Load, '../' );
 					this.workerSupport.setCallbacks( scopeBuilderFunc, scopeFuncComplete );
 					this.workerSupport.run(
 						{
@@ -173,7 +175,9 @@
 
 				var Parser  = ( function () {
 
-					function Parser( logger ) {
+					var ConsoleLogger = THREE.LoaderSupport.ConsoleLogger;
+
+					function Parser() {
 						this.sizeFactor = 0.5;
 						this.localOffsetFactor = 1.0;
 						this.globalObjectCount = 0;
@@ -181,10 +185,16 @@
 						this.dimension = 200;
 						this.quantity = 1;
 						this.callbackBuilder = null;
-						this.logger = logger;
+						this.callbackProgress = null;
+						this.logger = new ConsoleLogger();
 						this.serializedMaterials = null;
 					};
 
+					Parser.prototype.setLogConfig = function ( enabled, debug ) {
+						this.logger.setEnabled( enabled );
+						this.logger.setDebug( debug );
+					};
+
 					Parser.prototype.parse = function () {
 						var baseTriangle = [ 1.0, 1.0, 1.0, -1.0, 1.0, 1.0, 0.0, -1.0, 1.0 ];
 						var vertices = [];
@@ -389,12 +399,11 @@
 					var maxWebWorkers = 4;
 					var radius = 640;
 					var logger = new THREE.LoaderSupport.ConsoleLogger( false );
-					this.workerDirector = new THREE.LoaderSupport.WorkerDirector( MeshSpray, logger );
-					this.workerDirector.setCrossOrigin( 'anonymous' );
+					var workerDirector = new THREE.LoaderSupport.WorkerDirector( MeshSpray, logger );
+					workerDirector.setCrossOrigin( 'anonymous' );
 
-					var scope = this;
 					var callbackOnLoad = function ( event ) {
-						logger.logInfo( 'Worker #' + event.detail.instanceNo + ': Completed loading. (#' + scope.workerDirector.objectsCompleted + ')' );
+						logger.logInfo( 'Worker #' + event.detail.instanceNo + ': Completed loading. (#' + workerDirector.objectsCompleted + ')' );
 					};
 					var reportProgress = function( event ) {
 						document.getElementById( 'feedback' ).innerHTML = event.detail.text;
@@ -416,7 +425,7 @@
 					callbacks.setCallbackOnMeshAlter( callbackMeshAlter );
 					callbacks.setCallbackOnLoad( callbackOnLoad );
 					callbacks.setCallbackOnProgress( reportProgress );
-					this.workerDirector.prepareWorkers( callbacks, maxQueueSize, maxWebWorkers );
+					workerDirector.prepareWorkers( callbacks, maxQueueSize, maxWebWorkers );
 
 					var prepData;
 					var pivot;
@@ -440,9 +449,9 @@
 						prepData.dimension = Math.max( Math.random() * 500, 100 );
 						prepData.globalObjectCount = globalObjectCount++;
 
-						this.workerDirector.enqueueForRun( prepData );
+						workerDirector.enqueueForRun( prepData );
 					}
-					this.workerDirector.processQueue();
+					workerDirector.processQueue();
 				};
 
 				MeshSprayApp.prototype.resizeDisplayGL = function () {

+ 4 - 1
examples/webgl_loader_obj2_options.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <html lang="en">
 	<head>
-		<title>three.js webgl - WWOBJLoader2</title>
+		<title>three.js webgl - OBJLoader2 usage options</title>
 		<meta charset="utf-8">
 		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
 
@@ -243,6 +243,8 @@
 					var scope = this;
 					var objLoader = new THREE.OBJLoader2();
 					var callbackOnLoad = function ( event ) {
+						objLoader.workerSupport.setTerminateRequested( true );
+
 						var local = new THREE.Object3D();
 						local.name = 'Pivot_WaltHead';
 						local.position.set( -125, 50, 0 );
@@ -257,6 +259,7 @@
 					var onLoadMtl = function ( materials ) {
 						objLoader.setModelName( modelName );
 						objLoader.setMaterials( materials );
+						objLoader.terminateWorkerOnLoad = false;
 						objLoader.load( 'obj/walt/WaltHead.obj', callbackOnLoad, null, null, null, true );
 					};
 					objLoader.loadMtl( 'obj/walt//WaltHead.mtl', 'WaltHead.mtl', null, onLoadMtl );

+ 20 - 4
examples/webgl_loader_obj2_run_director.html

@@ -113,7 +113,7 @@
 
 					this.logger = new THREE.LoaderSupport.ConsoleLogger();
 					this.logger.setEnabled( false );
-					this.workerDirector = new THREE.LoaderSupport.WorkerDirector( THREE.OBJLoader2, this.logger  );
+					this.workerDirector = new THREE.LoaderSupport.WorkerDirector( THREE.OBJLoader2, this.logger );
 					this.workerDirector.setCrossOrigin( 'anonymous' );
 
 					this.controls = null;
@@ -380,7 +380,24 @@
 				};
 
 				WWParallels.prototype.terminateManager = function () {
-					this.workerDirector.deregister();
+					this.workerDirector.tearDown();
+					this.running = false;
+				};
+
+				WWParallels.prototype.terminateManagerAndClearScene = function () {
+					var scope = this;
+					var scopedClearAllAssests = function (){
+						scope.clearAllAssests();
+					};
+					if ( this.workerDirector.isRunning() ) {
+
+						this.workerDirector.tearDown( scopedClearAllAssests );
+
+					} else {
+
+						scopedClearAllAssests();
+					}
+
 					this.running = false;
 				};
 
@@ -402,8 +419,7 @@
 					app.terminateManager();
 				},
 				clearAllAssests: function () {
-					app.terminateManager();
-					app.clearAllAssests();
+					app.terminateManagerAndClearScene();
 				}
 			};
 			var gui = new dat.GUI( {

+ 1 - 1
examples/webgl_materials_cubemap_dynamic2.html

@@ -61,7 +61,7 @@
 				scene = new THREE.Scene();
 
 				var mesh = new THREE.Mesh( new THREE.SphereBufferGeometry( 500, 32, 16 ), new THREE.MeshBasicMaterial( { map: texture } ) );
-				mesh.scale.x = -1;
+				mesh.geometry.scale( - 1, 1, 1 );
 				scene.add( mesh );
 
 				renderer = new THREE.WebGLRenderer( { antialias: true } );

+ 1 - 1
examples/webgl_panorama_cube.html

@@ -75,7 +75,7 @@
 			}
 
 			var skyBox = new THREE.Mesh( new THREE.CubeGeometry( 1, 1, 1 ), materials );
-			skyBox.applyMatrix( new THREE.Matrix4().makeScale( 1, 1, - 1 ) );
+			skyBox.geometry.scale( 1, 1, - 1 );
 			scene.add( skyBox );
 
 			window.addEventListener( 'resize', onWindowResize, false );

+ 1 - 2
examples/webvr_panorama.html

@@ -48,6 +48,7 @@
 			camera.layers.enable( 1 );
 
 			var geometry = new THREE.CubeGeometry( 100, 100, 100 );
+			geometry.scale( 1, 1, - 1 );
 			var textures = getTexturesFromAtlasFile( "textures/cube/sun_temple_stripe_stereo.jpg", 12 );
 
 			var materials = [];
@@ -59,7 +60,6 @@
 			}
 
 			var skyBox = new THREE.Mesh( geometry, materials );
-			skyBox.applyMatrix( new THREE.Matrix4().makeScale( 1, 1, - 1 ) );
 			skyBox.layers.set( 1 );
 			scene.add( skyBox );
 
@@ -73,7 +73,6 @@
 			}
 
 			var skyBoxR = new THREE.Mesh( geometry, materialsR );
-			skyBoxR.applyMatrix( new THREE.Matrix4().makeScale( 1, 1, - 1 ) );
 			skyBoxR.layers.set( 2 );
 			scene.add( skyBoxR );
 

+ 1 - 1
examples/webvr_rollercoaster.html

@@ -55,7 +55,7 @@
 			// environment
 
 			var geometry = new THREE.PlaneBufferGeometry( 500, 500, 15, 15 );
-			geometry.applyMatrix( new THREE.Matrix4().makeRotationX( - Math.PI / 2 ) );
+			geometry.rotateX( - Math.PI / 2 );
 
 			var positions = geometry.attributes.position.array;
 			var vertex = new THREE.Vector3();

+ 0 - 2
examples/webvr_vive.html

@@ -165,11 +165,9 @@
 				// controllers
 
 				controller1 = new THREE.ViveController( 0 );
-				controller1.standingMatrix = renderer.vr.getStandingMatrix();
 				user.add( controller1 );
 
 				controller2 = new THREE.ViveController( 1 );
-				controller2.standingMatrix = renderer.vr.getStandingMatrix();
 				user.add( controller2 );
 
 				var loader = new THREE.OBJLoader();

+ 0 - 2
examples/webvr_vive_dragging.html

@@ -143,13 +143,11 @@
 				// controllers
 
 				controller1 = new THREE.ViveController( 0 );
-				controller1.standingMatrix = renderer.vr.getStandingMatrix();
 				controller1.addEventListener( 'triggerdown', onTriggerDown );
 				controller1.addEventListener( 'triggerup', onTriggerUp );
 				user.add( controller1 );
 
 				controller2 = new THREE.ViveController( 1 );
-				controller2.standingMatrix = renderer.vr.getStandingMatrix();
 				controller2.addEventListener( 'triggerdown', onTriggerDown );
 				controller2.addEventListener( 'triggerup', onTriggerUp );
 				user.add( controller2 );

+ 0 - 2
examples/webvr_vive_paint.html

@@ -142,13 +142,11 @@
 				// controllers
 
 				controller1 = new THREE.PaintViveController( 0 );
-				controller1.standingMatrix = renderer.vr.getStandingMatrix();
 				controller1.userData.points = [ new THREE.Vector3(), new THREE.Vector3() ];
 				controller1.userData.matrices = [ new THREE.Matrix4(), new THREE.Matrix4() ];
 				user.add( controller1 );
 
 				controller2 = new THREE.PaintViveController( 1 );
-				controller2.standingMatrix = renderer.vr.getStandingMatrix();
 				controller2.userData.points = [ new THREE.Vector3(), new THREE.Vector3() ];
 				controller2.userData.matrices = [ new THREE.Matrix4(), new THREE.Matrix4() ];
 				user.add( controller2 );

+ 0 - 2
examples/webvr_vive_sculpt.html

@@ -124,11 +124,9 @@
 				// controllers
 
 				controller1 = new THREE.ViveController( 0 );
-				controller1.standingMatrix = renderer.vr.getStandingMatrix();
 				user.add( controller1 );
 
 				controller2 = new THREE.ViveController( 1 );
-				controller2.standingMatrix = renderer.vr.getStandingMatrix();
 				user.add( controller2 );
 
 				var loader = new THREE.OBJLoader();

+ 25 - 0
src/Three.Legacy.js

@@ -68,6 +68,7 @@ import { Skeleton } from './objects/Skeleton.js';
 import { WebGLRenderer } from './renderers/WebGLRenderer.js';
 import { WebGLRenderTarget } from './renderers/WebGLRenderTarget.js';
 import { WebGLShadowMap } from './renderers/webgl/WebGLShadowMap.js';
+import { WebVRManager } from './renderers/webvr/WebVRManager.js';
 import { Shape } from './extras/core/Shape.js';
 import { CubeCamera } from './cameras/CubeCamera.js';
 
@@ -1534,6 +1535,30 @@ Object.defineProperties( WebGLRenderTarget.prototype, {
 
 //
 
+Object.assign( WebVRManager.prototype, {
+
+	getStandingMatrix: function () {
+
+		console.warn( 'THREE.WebVRManager: .getStandingMatrix() has been removed.' );
+
+	}
+
+} );
+
+Object.defineProperties( WebVRManager.prototype, {
+
+	standing: {
+		set: function ( /* value */ ) {
+
+			console.warn( 'THREE.WebVRManager: .standing has been removed.' );
+
+		}
+	}
+
+} );
+
+//
+
 Audio.prototype.load = function ( file ) {
 
 	console.warn( 'THREE.Audio: .load has been deprecated. Use THREE.AudioLoader instead.' );

+ 810 - 0
src/extras/Earcut.js

@@ -0,0 +1,810 @@
+/**
+ * @author Mugen87 / https://github.com/Mugen87
+ * Port from https://github.com/mapbox/earcut (v2.1.2)
+ */
+
+var Earcut = {
+
+	triangulate: function ( data, holeIndices, dim ) {
+
+		dim = dim || 2;
+
+		var hasHoles = holeIndices && holeIndices.length,
+			outerLen = hasHoles ? holeIndices[ 0 ] * dim : data.length,
+			outerNode = linkedList( data, 0, outerLen, dim, true ),
+			triangles = [];
+
+		if ( ! outerNode ) return triangles;
+
+		var minX, minY, maxX, maxY, x, y, invSize;
+
+		if ( hasHoles ) outerNode = eliminateHoles( data, holeIndices, outerNode, dim );
+
+		// if the shape is not too simple, we'll use z-order curve hash later; calculate polygon bbox
+
+		if ( data.length > 80 * dim ) {
+
+			minX = maxX = data[ 0 ];
+			minY = maxY = data[ 1 ];
+
+			for ( var i = dim; i < outerLen; i += dim ) {
+
+				x = data[ i ];
+				y = data[ i + 1 ];
+				if ( x < minX ) minX = x;
+				if ( y < minY ) minY = y;
+				if ( x > maxX ) maxX = x;
+				if ( y > maxY ) maxY = y;
+
+			}
+
+			// minX, minY and invSize are later used to transform coords into integers for z-order calculation
+
+			invSize = Math.max( maxX - minX, maxY - minY );
+			invSize = invSize !== 0 ? 1 / invSize : 0;
+
+		}
+
+		earcutLinked( outerNode, triangles, dim, minX, minY, invSize );
+
+		return triangles;
+
+	}
+
+};
+
+// create a circular doubly linked list from polygon points in the specified winding order
+
+function linkedList( data, start, end, dim, clockwise ) {
+
+	var i, last;
+
+	if ( clockwise === ( signedArea( data, start, end, dim ) > 0 ) ) {
+
+		for ( i = start; i < end; i += dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last );
+
+	} else {
+
+		for ( i = end - dim; i >= start; i -= dim ) last = insertNode( i, data[ i ], data[ i + 1 ], last );
+
+	}
+
+	if ( last && equals( last, last.next ) ) {
+
+		removeNode( last );
+		last = last.next;
+
+	}
+
+	return last;
+
+}
+
+// eliminate colinear or duplicate points
+
+function filterPoints( start, end ) {
+
+	if ( ! start ) return start;
+	if ( ! end ) end = start;
+
+	var p = start, again;
+
+	do {
+
+		again = false;
+
+		if ( ! p.steiner && ( equals( p, p.next ) || area( p.prev, p, p.next ) === 0 ) ) {
+
+			removeNode( p );
+			p = end = p.prev;
+			if ( p === p.next ) break;
+			again = true;
+
+		} else {
+
+			p = p.next;
+
+		}
+
+	} while ( again || p !== end );
+
+	return end;
+
+}
+
+// main ear slicing loop which triangulates a polygon (given as a linked list)
+
+function earcutLinked( ear, triangles, dim, minX, minY, invSize, pass ) {
+
+	if ( ! ear ) return;
+
+	// interlink polygon nodes in z-order
+
+	if ( ! pass && invSize ) indexCurve( ear, minX, minY, invSize );
+
+	var stop = ear, prev, next;
+
+	// iterate through ears, slicing them one by one
+
+	while ( ear.prev !== ear.next ) {
+
+		prev = ear.prev;
+		next = ear.next;
+
+		if ( invSize ? isEarHashed( ear, minX, minY, invSize ) : isEar( ear ) ) {
+
+			// cut off the triangle
+			triangles.push( prev.i / dim );
+			triangles.push( ear.i / dim );
+			triangles.push( next.i / dim );
+
+			removeNode( ear );
+
+			// skipping the next vertice leads to less sliver triangles
+			ear = next.next;
+			stop = next.next;
+
+			continue;
+
+		}
+
+		ear = next;
+
+		// if we looped through the whole remaining polygon and can't find any more ears
+
+		if ( ear === stop ) {
+
+			// try filtering points and slicing again
+
+			if ( ! pass ) {
+
+				earcutLinked( filterPoints( ear ), triangles, dim, minX, minY, invSize, 1 );
+
+				// if this didn't work, try curing all small self-intersections locally
+
+			} else if ( pass === 1 ) {
+
+				ear = cureLocalIntersections( ear, triangles, dim );
+				earcutLinked( ear, triangles, dim, minX, minY, invSize, 2 );
+
+			// as a last resort, try splitting the remaining polygon into two
+
+			} else if ( pass === 2 ) {
+
+				splitEarcut( ear, triangles, dim, minX, minY, invSize );
+
+			}
+
+			break;
+
+		}
+
+	}
+
+}
+
+// check whether a polygon node forms a valid ear with adjacent nodes
+
+function isEar( ear ) {
+
+	var a = ear.prev,
+		b = ear,
+		c = ear.next;
+
+	if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear
+
+	// now make sure we don't have other points inside the potential ear
+	var p = ear.next.next;
+
+	while ( p !== ear.prev ) {
+
+		if ( pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) && area( p.prev, p, p.next ) >= 0 ) {
+
+			return false;
+
+		}
+
+		p = p.next;
+
+	}
+
+	return true;
+
+}
+
+function isEarHashed( ear, minX, minY, invSize ) {
+
+	var a = ear.prev,
+		b = ear,
+		c = ear.next;
+
+	if ( area( a, b, c ) >= 0 ) return false; // reflex, can't be an ear
+
+	// triangle bbox; min & max are calculated like this for speed
+
+	var minTX = a.x < b.x ? ( a.x < c.x ? a.x : c.x ) : ( b.x < c.x ? b.x : c.x ),
+		minTY = a.y < b.y ? ( a.y < c.y ? a.y : c.y ) : ( b.y < c.y ? b.y : c.y ),
+		maxTX = a.x > b.x ? ( a.x > c.x ? a.x : c.x ) : ( b.x > c.x ? b.x : c.x ),
+		maxTY = a.y > b.y ? ( a.y > c.y ? a.y : c.y ) : ( b.y > c.y ? b.y : c.y );
+
+	// z-order range for the current triangle bbox;
+
+	var minZ = zOrder( minTX, minTY, minX, minY, invSize ),
+		maxZ = zOrder( maxTX, maxTY, minX, minY, invSize );
+
+	// first look for points inside the triangle in increasing z-order
+
+	var p = ear.nextZ;
+
+	while ( p && p.z <= maxZ ) {
+
+		if ( p !== ear.prev && p !== ear.next &&
+				pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) &&
+				area( p.prev, p, p.next ) >= 0 ) return false;
+		p = p.nextZ;
+
+	}
+
+	// then look for points in decreasing z-order
+
+	p = ear.prevZ;
+
+	while ( p && p.z >= minZ ) {
+
+		if ( p !== ear.prev && p !== ear.next &&
+				pointInTriangle( a.x, a.y, b.x, b.y, c.x, c.y, p.x, p.y ) &&
+				area( p.prev, p, p.next ) >= 0 ) return false;
+
+		p = p.prevZ;
+
+	}
+
+	return true;
+
+}
+
+// go through all polygon nodes and cure small local self-intersections
+
+function cureLocalIntersections( start, triangles, dim ) {
+
+	var p = start;
+
+	do {
+
+		var a = p.prev, b = p.next.next;
+
+		if ( ! equals( a, b ) && intersects( a, p, p.next, b ) && locallyInside( a, b ) && locallyInside( b, a ) ) {
+
+			triangles.push( a.i / dim );
+			triangles.push( p.i / dim );
+			triangles.push( b.i / dim );
+
+			// remove two nodes involved
+
+			removeNode( p );
+			removeNode( p.next );
+
+			p = start = b;
+
+		}
+
+		p = p.next;
+
+	} while ( p !== start );
+
+	return p;
+
+}
+
+// try splitting polygon into two and triangulate them independently
+
+function splitEarcut( start, triangles, dim, minX, minY, invSize ) {
+
+	// look for a valid diagonal that divides the polygon into two
+
+	var a = start;
+
+	do {
+
+		var b = a.next.next;
+
+		while ( b !== a.prev ) {
+
+			if ( a.i !== b.i && isValidDiagonal( a, b ) ) {
+
+				// split the polygon in two by the diagonal
+
+				var c = splitPolygon( a, b );
+
+				// filter colinear points around the cuts
+
+				a = filterPoints( a, a.next );
+				c = filterPoints( c, c.next );
+
+				// run earcut on each half
+
+				earcutLinked( a, triangles, dim, minX, minY, invSize );
+				earcutLinked( c, triangles, dim, minX, minY, invSize );
+				return;
+
+			}
+
+			b = b.next;
+
+		}
+
+		a = a.next;
+
+	} while ( a !== start );
+
+}
+
+// link every hole into the outer loop, producing a single-ring polygon without holes
+
+function eliminateHoles( data, holeIndices, outerNode, dim ) {
+
+	var queue = [], i, len, start, end, list;
+
+	for ( i = 0, len = holeIndices.length; i < len; i ++ ) {
+
+		start = holeIndices[ i ] * dim;
+		end = i < len - 1 ? holeIndices[ i + 1 ] * dim : data.length;
+		list = linkedList( data, start, end, dim, false );
+		if ( list === list.next ) list.steiner = true;
+		queue.push( getLeftmost( list ) );
+
+	}
+
+	queue.sort( compareX );
+
+	// process holes from left to right
+
+	for ( i = 0; i < queue.length; i ++ ) {
+
+		eliminateHole( queue[ i ], outerNode );
+		outerNode = filterPoints( outerNode, outerNode.next );
+
+	}
+
+	return outerNode;
+
+}
+
+function compareX( a, b ) {
+
+	return a.x - b.x;
+
+}
+
+// find a bridge between vertices that connects hole with an outer ring and and link it
+
+function eliminateHole( hole, outerNode ) {
+
+	outerNode = findHoleBridge( hole, outerNode );
+
+	if ( outerNode ) {
+
+		var b = splitPolygon( outerNode, hole );
+
+		filterPoints( b, b.next );
+
+	}
+
+}
+
+// David Eberly's algorithm for finding a bridge between hole and outer polygon
+
+function findHoleBridge( hole, outerNode ) {
+
+	var p = outerNode,
+		hx = hole.x,
+		hy = hole.y,
+		qx = - Infinity,
+		m;
+
+	// find a segment intersected by a ray from the hole's leftmost point to the left;
+	// segment's endpoint with lesser x will be potential connection point
+
+	do {
+
+		if ( hy <= p.y && hy >= p.next.y && p.next.y !== p.y ) {
+
+			var x = p.x + ( hy - p.y ) * ( p.next.x - p.x ) / ( p.next.y - p.y );
+
+			if ( x <= hx && x > qx ) {
+
+				qx = x;
+
+				if ( x === hx ) {
+
+					if ( hy === p.y ) return p;
+					if ( hy === p.next.y ) return p.next;
+
+				}
+
+				m = p.x < p.next.x ? p : p.next;
+
+			}
+
+		}
+
+		p = p.next;
+
+	} while ( p !== outerNode );
+
+	if ( ! m ) return null;
+
+	if ( hx === qx ) return m.prev; // hole touches outer segment; pick lower endpoint
+
+	// look for points inside the triangle of hole point, segment intersection and endpoint;
+	// if there are no points found, we have a valid connection;
+	// otherwise choose the point of the minimum angle with the ray as connection point
+
+	var stop = m,
+		mx = m.x,
+		my = m.y,
+		tanMin = Infinity,
+		tan;
+
+	p = m.next;
+
+	while ( p !== stop ) {
+
+		if ( hx >= p.x && p.x >= mx && hx !== p.x &&
+						pointInTriangle( hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y ) ) {
+
+			tan = Math.abs( hy - p.y ) / ( hx - p.x ); // tangential
+
+			if ( ( tan < tanMin || ( tan === tanMin && p.x > m.x ) ) && locallyInside( p, hole ) ) {
+
+				m = p;
+				tanMin = tan;
+
+			}
+
+		}
+
+		p = p.next;
+
+	}
+
+	return m;
+
+}
+
+// interlink polygon nodes in z-order
+
+function indexCurve( start, minX, minY, invSize ) {
+
+	var p = start;
+
+	do {
+
+		if ( p.z === null ) p.z = zOrder( p.x, p.y, minX, minY, invSize );
+		p.prevZ = p.prev;
+		p.nextZ = p.next;
+		p = p.next;
+
+	} while ( p !== start );
+
+	p.prevZ.nextZ = null;
+	p.prevZ = null;
+
+	sortLinked( p );
+
+}
+
+// Simon Tatham's linked list merge sort algorithm
+// http://www.chiark.greenend.org.uk/~sgtatham/algorithms/listsort.html
+
+function sortLinked( list ) {
+
+	var i, p, q, e, tail, numMerges, pSize, qSize, inSize = 1;
+
+	do {
+
+		p = list;
+		list = null;
+		tail = null;
+		numMerges = 0;
+
+		while ( p ) {
+
+			numMerges ++;
+			q = p;
+			pSize = 0;
+
+			for ( i = 0; i < inSize; i ++ ) {
+
+				pSize ++;
+				q = q.nextZ;
+				if ( ! q ) break;
+
+			}
+
+			qSize = inSize;
+
+			while ( pSize > 0 || ( qSize > 0 && q ) ) {
+
+				if ( pSize !== 0 && ( qSize === 0 || ! q || p.z <= q.z ) ) {
+
+					e = p;
+					p = p.nextZ;
+					pSize --;
+
+				} else {
+
+					e = q;
+					q = q.nextZ;
+					qSize --;
+
+				}
+
+				if ( tail ) tail.nextZ = e;
+				else list = e;
+
+				e.prevZ = tail;
+				tail = e;
+
+			}
+
+			p = q;
+
+		}
+
+		tail.nextZ = null;
+		inSize *= 2;
+
+	} while ( numMerges > 1 );
+
+	return list;
+
+}
+
+// z-order of a point given coords and inverse of the longer side of data bbox
+
+function zOrder( x, y, minX, minY, invSize ) {
+
+	// coords are transformed into non-negative 15-bit integer range
+
+	x = 32767 * ( x - minX ) * invSize;
+	y = 32767 * ( y - minY ) * invSize;
+
+	x = ( x | ( x << 8 ) ) & 0x00FF00FF;
+	x = ( x | ( x << 4 ) ) & 0x0F0F0F0F;
+	x = ( x | ( x << 2 ) ) & 0x33333333;
+	x = ( x | ( x << 1 ) ) & 0x55555555;
+
+	y = ( y | ( y << 8 ) ) & 0x00FF00FF;
+	y = ( y | ( y << 4 ) ) & 0x0F0F0F0F;
+	y = ( y | ( y << 2 ) ) & 0x33333333;
+	y = ( y | ( y << 1 ) ) & 0x55555555;
+
+	return x | ( y << 1 );
+
+}
+
+// find the leftmost node of a polygon ring
+
+function getLeftmost( start ) {
+
+	var p = start, leftmost = start;
+
+	do {
+
+		if ( p.x < leftmost.x ) leftmost = p;
+		p = p.next;
+
+	} while ( p !== start );
+
+	return leftmost;
+
+}
+
+// check if a point lies within a convex triangle
+
+function pointInTriangle( ax, ay, bx, by, cx, cy, px, py ) {
+
+	return ( cx - px ) * ( ay - py ) - ( ax - px ) * ( cy - py ) >= 0 &&
+	 ( ax - px ) * ( by - py ) - ( bx - px ) * ( ay - py ) >= 0 &&
+	 ( bx - px ) * ( cy - py ) - ( cx - px ) * ( by - py ) >= 0;
+
+}
+
+// check if a diagonal between two polygon nodes is valid (lies in polygon interior)
+
+function isValidDiagonal( a, b ) {
+
+	return a.next.i !== b.i && a.prev.i !== b.i && ! intersectsPolygon( a, b ) &&
+		locallyInside( a, b ) && locallyInside( b, a ) && middleInside( a, b );
+
+}
+
+// signed area of a triangle
+
+function area( p, q, r ) {
+
+	return ( q.y - p.y ) * ( r.x - q.x ) - ( q.x - p.x ) * ( r.y - q.y );
+
+}
+
+// check if two points are equal
+
+function equals( p1, p2 ) {
+
+	return p1.x === p2.x && p1.y === p2.y;
+
+}
+
+// check if two segments intersect
+
+function intersects( p1, q1, p2, q2 ) {
+
+	if ( ( equals( p1, q1 ) && equals( p2, q2 ) ) ||
+			( equals( p1, q2 ) && equals( p2, q1 ) ) ) return true;
+
+	return area( p1, q1, p2 ) > 0 !== area( p1, q1, q2 ) > 0 &&
+				 area( p2, q2, p1 ) > 0 !== area( p2, q2, q1 ) > 0;
+
+}
+
+// check if a polygon diagonal intersects any polygon segments
+
+function intersectsPolygon( a, b ) {
+
+	var p = a;
+
+	do {
+
+		if ( p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i &&
+						intersects( p, p.next, a, b ) ) {
+
+			return true;
+
+		}
+
+		p = p.next;
+
+	} while ( p !== a );
+
+	return false;
+
+}
+
+// check if a polygon diagonal is locally inside the polygon
+
+function locallyInside( a, b ) {
+
+	return area( a.prev, a, a.next ) < 0 ?
+		area( a, b, a.next ) >= 0 && area( a, a.prev, b ) >= 0 :
+		area( a, b, a.prev ) < 0 || area( a, a.next, b ) < 0;
+
+}
+
+// check if the middle point of a polygon diagonal is inside the polygon
+
+function middleInside( a, b ) {
+
+	var p = a,
+		inside = false,
+		px = ( a.x + b.x ) / 2,
+		py = ( a.y + b.y ) / 2;
+
+	do {
+
+		if ( ( ( p.y > py ) !== ( p.next.y > py ) ) && p.next.y !== p.y &&
+						( px < ( p.next.x - p.x ) * ( py - p.y ) / ( p.next.y - p.y ) + p.x ) ) {
+
+			inside = ! inside;
+
+		}
+
+		p = p.next;
+
+	} while ( p !== a );
+
+	return inside;
+
+}
+
+// link two polygon vertices with a bridge; if the vertices belong to the same ring, it splits polygon into two;
+// if one belongs to the outer ring and another to a hole, it merges it into a single ring
+
+function splitPolygon( a, b ) {
+
+	var a2 = new Node( a.i, a.x, a.y ),
+		b2 = new Node( b.i, b.x, b.y ),
+		an = a.next,
+		bp = b.prev;
+
+	a.next = b;
+	b.prev = a;
+
+	a2.next = an;
+	an.prev = a2;
+
+	b2.next = a2;
+	a2.prev = b2;
+
+	bp.next = b2;
+	b2.prev = bp;
+
+	return b2;
+
+}
+
+// create a node and optionally link it with previous one (in a circular doubly linked list)
+
+function insertNode( i, x, y, last ) {
+
+	var p = new Node( i, x, y );
+
+	if ( ! last ) {
+
+		p.prev = p;
+		p.next = p;
+
+	} else {
+
+		p.next = last.next;
+		p.prev = last;
+		last.next.prev = p;
+		last.next = p;
+
+	}
+
+	return p;
+
+}
+
+function removeNode( p ) {
+
+	p.next.prev = p.prev;
+	p.prev.next = p.next;
+
+	if ( p.prevZ ) p.prevZ.nextZ = p.nextZ;
+	if ( p.nextZ ) p.nextZ.prevZ = p.prevZ;
+
+}
+
+function Node( i, x, y ) {
+
+	// vertice index in coordinates array
+	this.i = i;
+
+	// vertex coordinates
+	this.x = x;
+	this.y = y;
+
+	// previous and next vertice nodes in a polygon ring
+	this.prev = null;
+	this.next = null;
+
+	// z-order curve value
+	this.z = null;
+
+	// previous and next nodes in z-order
+	this.prevZ = null;
+	this.nextZ = null;
+
+	// indicates whether this is a steiner point
+	this.steiner = false;
+
+}
+
+function signedArea( data, start, end, dim ) {
+
+	var sum = 0;
+
+	for ( var i = start, j = end - dim; i < end; i += dim ) {
+
+		sum += ( data[ j ] - data[ i ] ) * ( data[ i + 1 ] + data[ j + 1 ] );
+		j = i;
+
+	}
+
+	return sum;
+
+}
+
+export { Earcut };

+ 27 - 625
src/extras/ShapeUtils.js

@@ -2,6 +2,8 @@
  * @author zz85 / http://www.lab4games.net/zz85/blog
  */
 
+import { Earcut } from './Earcut.js';
+
 var ShapeUtils = {
 
 	// calculate area of the contour polygon
@@ -21,173 +23,11 @@ var ShapeUtils = {
 
 	},
 
-	triangulate: ( function () {
-
-		/**
-		 * This code is a quick port of code written in C++ which was submitted to
-		 * flipcode.com by John W. Ratcliff  // July 22, 2000
-		 * See original code and more information here:
-		 * http://www.flipcode.com/archives/Efficient_Polygon_Triangulation.shtml
-		 *
-		 * ported to actionscript by Zevan Rosser
-		 * www.actionsnippet.com
-		 *
-		 * ported to javascript by Joshua Koo
-		 * http://www.lab4games.net/zz85/blog
-		 *
-		 */
-
-		function snip( contour, u, v, w, n, verts ) {
-
-			var p;
-			var ax, ay, bx, by;
-			var cx, cy, px, py;
-
-			ax = contour[ verts[ u ] ].x;
-			ay = contour[ verts[ u ] ].y;
-
-			bx = contour[ verts[ v ] ].x;
-			by = contour[ verts[ v ] ].y;
-
-			cx = contour[ verts[ w ] ].x;
-			cy = contour[ verts[ w ] ].y;
-
-			if ( ( bx - ax ) * ( cy - ay ) - ( by - ay ) * ( cx - ax ) <= 0 ) return false;
-
-			var aX, aY, bX, bY, cX, cY;
-			var apx, apy, bpx, bpy, cpx, cpy;
-			var cCROSSap, bCROSScp, aCROSSbp;
-
-			aX = cx - bx; aY = cy - by;
-			bX = ax - cx; bY = ay - cy;
-			cX = bx - ax; cY = by - ay;
-
-			for ( p = 0; p < n; p ++ ) {
-
-				px = contour[ verts[ p ] ].x;
-				py = contour[ verts[ p ] ].y;
-
-				if ( ( ( px === ax ) && ( py === ay ) ) ||
-					 ( ( px === bx ) && ( py === by ) ) ||
-					 ( ( px === cx ) && ( py === cy ) ) )	continue;
-
-				apx = px - ax; apy = py - ay;
-				bpx = px - bx; bpy = py - by;
-				cpx = px - cx; cpy = py - cy;
-
-				// see if p is inside triangle abc
-
-				aCROSSbp = aX * bpy - aY * bpx;
-				cCROSSap = cX * apy - cY * apx;
-				bCROSScp = bX * cpy - bY * cpx;
-
-				if ( ( aCROSSbp >= - Number.EPSILON ) && ( bCROSScp >= - Number.EPSILON ) && ( cCROSSap >= - Number.EPSILON ) ) return false;
-
-			}
-
-			return true;
-
-		}
-
-		// takes in an contour array and returns
-
-		return function triangulate( contour, indices ) {
-
-			var n = contour.length;
-
-			if ( n < 3 ) return null;
-
-			var result = [],
-				verts = [],
-				vertIndices = [];
-
-			/* we want a counter-clockwise polygon in verts */
-
-			var u, v, w;
-
-			if ( ShapeUtils.area( contour ) > 0.0 ) {
-
-				for ( v = 0; v < n; v ++ ) verts[ v ] = v;
-
-			} else {
-
-				for ( v = 0; v < n; v ++ ) verts[ v ] = ( n - 1 ) - v;
-
-			}
-
-			var nv = n;
-
-			/*  remove nv - 2 vertices, creating 1 triangle every time */
-
-			var count = 2 * nv; /* error detection */
-
-			for ( v = nv - 1; nv > 2; ) {
-
-				/* if we loop, it is probably a non-simple polygon */
-
-				if ( ( count -- ) <= 0 ) {
-
-					//** Triangulate: ERROR - probable bad polygon!
-
-					//throw ( "Warning, unable to triangulate polygon!" );
-					//return null;
-					// Sometimes warning is fine, especially polygons are triangulated in reverse.
-					console.warn( 'THREE.ShapeUtils: Unable to triangulate polygon! in triangulate()' );
-
-					if ( indices ) return vertIndices;
-					return result;
-
-				}
-
-				/* three consecutive vertices in current polygon, <u,v,w> */
-
-				u = v; if ( nv <= u ) u = 0; /* previous */
-				v = u + 1; if ( nv <= v ) v = 0; /* new v    */
-				w = v + 1; if ( nv <= w ) w = 0; /* next     */
-
-				if ( snip( contour, u, v, w, nv, verts ) ) {
-
-					var a, b, c, s, t;
-
-					/* true names of the vertices */
-
-					a = verts[ u ];
-					b = verts[ v ];
-					c = verts[ w ];
-
-					/* output Triangle */
-
-					result.push( [ contour[ a ],
-						contour[ b ],
-						contour[ c ] ] );
-
-
-					vertIndices.push( [ verts[ u ], verts[ v ], verts[ w ] ] );
-
-					/* remove v from the remaining polygon */
-
-					for ( s = v, t = v + 1; t < nv; s ++, t ++ ) {
-
-						verts[ s ] = verts[ t ];
-
-					}
-
-					nv --;
-
-					/* reset error detection counter */
-
-					count = 2 * nv;
-
-				}
-
-			}
-
-			if ( indices ) return vertIndices;
-			return result;
+	isClockWise: function ( pts ) {
 
-		};
+		return ShapeUtils.area( pts ) < 0;
 
-	} )(),
+	},
 
 	triangulateShape: function ( contour, holes ) {
 
@@ -203,488 +43,50 @@ var ShapeUtils = {
 
 		}
 
-		removeDupEndPts( contour );
-		holes.forEach( removeDupEndPts );
-
-		function point_in_segment_2D_colin( inSegPt1, inSegPt2, inOtherPt ) {
-
-			// inOtherPt needs to be collinear to the inSegment
-			if ( inSegPt1.x !== inSegPt2.x ) {
-
-				if ( inSegPt1.x < inSegPt2.x ) {
-
-					return	( ( inSegPt1.x <= inOtherPt.x ) && ( inOtherPt.x <= inSegPt2.x ) );
-
-				} else {
-
-					return	( ( inSegPt2.x <= inOtherPt.x ) && ( inOtherPt.x <= inSegPt1.x ) );
-
-				}
-
-			} else {
-
-				if ( inSegPt1.y < inSegPt2.y ) {
-
-					return	( ( inSegPt1.y <= inOtherPt.y ) && ( inOtherPt.y <= inSegPt2.y ) );
-
-				} else {
-
-					return	( ( inSegPt2.y <= inOtherPt.y ) && ( inOtherPt.y <= inSegPt1.y ) );
-
-				}
-
-			}
-
-		}
-
-		function intersect_segments_2D( inSeg1Pt1, inSeg1Pt2, inSeg2Pt1, inSeg2Pt2, inExcludeAdjacentSegs ) {
-
-			var seg1dx = inSeg1Pt2.x - inSeg1Pt1.x, seg1dy = inSeg1Pt2.y - inSeg1Pt1.y;
-			var seg2dx = inSeg2Pt2.x - inSeg2Pt1.x, seg2dy = inSeg2Pt2.y - inSeg2Pt1.y;
-
-			var seg1seg2dx = inSeg1Pt1.x - inSeg2Pt1.x;
-			var seg1seg2dy = inSeg1Pt1.y - inSeg2Pt1.y;
-
-			var limit		= seg1dy * seg2dx - seg1dx * seg2dy;
-			var perpSeg1	= seg1dy * seg1seg2dx - seg1dx * seg1seg2dy;
-
-			if ( Math.abs( limit ) > Number.EPSILON ) {
-
-				// not parallel
-
-				var perpSeg2;
-				if ( limit > 0 ) {
-
-					if ( ( perpSeg1 < 0 ) || ( perpSeg1 > limit ) ) 		return [];
-					perpSeg2 = seg2dy * seg1seg2dx - seg2dx * seg1seg2dy;
-					if ( ( perpSeg2 < 0 ) || ( perpSeg2 > limit ) ) 		return [];
-
-				} else {
-
-					if ( ( perpSeg1 > 0 ) || ( perpSeg1 < limit ) ) 		return [];
-					perpSeg2 = seg2dy * seg1seg2dx - seg2dx * seg1seg2dy;
-					if ( ( perpSeg2 > 0 ) || ( perpSeg2 < limit ) ) 		return [];
-
-				}
-
-				// i.e. to reduce rounding errors
-				// intersection at endpoint of segment#1?
-				if ( perpSeg2 === 0 ) {
-
-					if ( ( inExcludeAdjacentSegs ) &&
-						 ( ( perpSeg1 === 0 ) || ( perpSeg1 === limit ) ) )		return [];
-					return [ inSeg1Pt1 ];
-
-				}
-				if ( perpSeg2 === limit ) {
-
-					if ( ( inExcludeAdjacentSegs ) &&
-						 ( ( perpSeg1 === 0 ) || ( perpSeg1 === limit ) ) )		return [];
-					return [ inSeg1Pt2 ];
-
-				}
-				// intersection at endpoint of segment#2?
-				if ( perpSeg1 === 0 )		return [ inSeg2Pt1 ];
-				if ( perpSeg1 === limit )	return [ inSeg2Pt2 ];
-
-				// return real intersection point
-				var factorSeg1 = perpSeg2 / limit;
-				return	[ { x: inSeg1Pt1.x + factorSeg1 * seg1dx, y: inSeg1Pt1.y + factorSeg1 * seg1dy } ];
-
-			} else {
-
-				// parallel or collinear
-				if ( ( perpSeg1 !== 0 ) ||
-					 ( seg2dy * seg1seg2dx !== seg2dx * seg1seg2dy ) ) 			return [];
-
-				// they are collinear or degenerate
-				var seg1Pt = ( ( seg1dx === 0 ) && ( seg1dy === 0 ) );	// segment1 is just a point?
-				var seg2Pt = ( ( seg2dx === 0 ) && ( seg2dy === 0 ) );	// segment2 is just a point?
-				// both segments are points
-				if ( seg1Pt && seg2Pt ) {
-
-					if ( ( inSeg1Pt1.x !== inSeg2Pt1.x ) ||
-						 ( inSeg1Pt1.y !== inSeg2Pt1.y ) )		return [];	// they are distinct  points
-					return [ inSeg1Pt1 ];	// they are the same point
-
-				}
-				// segment#1  is a single point
-				if ( seg1Pt ) {
-
-					if ( ! point_in_segment_2D_colin( inSeg2Pt1, inSeg2Pt2, inSeg1Pt1 ) )		return [];		// but not in segment#2
-					return [ inSeg1Pt1 ];
-
-				}
-				// segment#2  is a single point
-				if ( seg2Pt ) {
-
-					if ( ! point_in_segment_2D_colin( inSeg1Pt1, inSeg1Pt2, inSeg2Pt1 ) )		return [];		// but not in segment#1
-					return [ inSeg2Pt1 ];
-
-				}
-
-				// they are collinear segments, which might overlap
-				var seg1min, seg1max, seg1minVal, seg1maxVal;
-				var seg2min, seg2max, seg2minVal, seg2maxVal;
-				if ( seg1dx !== 0 ) {
-
-					// the segments are NOT on a vertical line
-					if ( inSeg1Pt1.x < inSeg1Pt2.x ) {
-
-						seg1min = inSeg1Pt1; seg1minVal = inSeg1Pt1.x;
-						seg1max = inSeg1Pt2; seg1maxVal = inSeg1Pt2.x;
-
-					} else {
-
-						seg1min = inSeg1Pt2; seg1minVal = inSeg1Pt2.x;
-						seg1max = inSeg1Pt1; seg1maxVal = inSeg1Pt1.x;
-
-					}
-					if ( inSeg2Pt1.x < inSeg2Pt2.x ) {
-
-						seg2min = inSeg2Pt1; seg2minVal = inSeg2Pt1.x;
-						seg2max = inSeg2Pt2; seg2maxVal = inSeg2Pt2.x;
-
-					} else {
-
-						seg2min = inSeg2Pt2; seg2minVal = inSeg2Pt2.x;
-						seg2max = inSeg2Pt1; seg2maxVal = inSeg2Pt1.x;
-
-					}
-
-				} else {
-
-					// the segments are on a vertical line
-					if ( inSeg1Pt1.y < inSeg1Pt2.y ) {
-
-						seg1min = inSeg1Pt1; seg1minVal = inSeg1Pt1.y;
-						seg1max = inSeg1Pt2; seg1maxVal = inSeg1Pt2.y;
-
-					} else {
-
-						seg1min = inSeg1Pt2; seg1minVal = inSeg1Pt2.y;
-						seg1max = inSeg1Pt1; seg1maxVal = inSeg1Pt1.y;
-
-					}
-					if ( inSeg2Pt1.y < inSeg2Pt2.y ) {
-
-						seg2min = inSeg2Pt1; seg2minVal = inSeg2Pt1.y;
-						seg2max = inSeg2Pt2; seg2maxVal = inSeg2Pt2.y;
-
-					} else {
-
-						seg2min = inSeg2Pt2; seg2minVal = inSeg2Pt2.y;
-						seg2max = inSeg2Pt1; seg2maxVal = inSeg2Pt1.y;
-
-					}
-
-				}
-				if ( seg1minVal <= seg2minVal ) {
-
-					if ( seg1maxVal < seg2minVal )	return [];
-					if ( seg1maxVal === seg2minVal )	{
-
-						if ( inExcludeAdjacentSegs )		return [];
-						return [ seg2min ];
-
-					}
-					if ( seg1maxVal <= seg2maxVal )	return [ seg2min, seg1max ];
-					return	[ seg2min, seg2max ];
-
-				} else {
-
-					if ( seg1minVal > seg2maxVal )	return [];
-					if ( seg1minVal === seg2maxVal )	{
-
-						if ( inExcludeAdjacentSegs )		return [];
-						return [ seg1min ];
-
-					}
-					if ( seg1maxVal <= seg2maxVal )	return [ seg1min, seg1max ];
-					return	[ seg1min, seg2max ];
-
-				}
-
-			}
-
-		}
-
-		function isPointInsideAngle( inVertex, inLegFromPt, inLegToPt, inOtherPt ) {
-
-			// The order of legs is important
-
-			// translation of all points, so that Vertex is at (0,0)
-			var legFromPtX	= inLegFromPt.x - inVertex.x, legFromPtY = inLegFromPt.y - inVertex.y;
-			var legToPtX	= inLegToPt.x	- inVertex.x, legToPtY = inLegToPt.y	- inVertex.y;
-			var otherPtX	= inOtherPt.x	- inVertex.x, otherPtY = inOtherPt.y	- inVertex.y;
-
-			// main angle >0: < 180 deg.; 0: 180 deg.; <0: > 180 deg.
-			var from2toAngle	= legFromPtX * legToPtY - legFromPtY * legToPtX;
-			var from2otherAngle	= legFromPtX * otherPtY - legFromPtY * otherPtX;
-
-			if ( Math.abs( from2toAngle ) > Number.EPSILON ) {
-
-				// angle != 180 deg.
-
-				var other2toAngle		= otherPtX * legToPtY - otherPtY * legToPtX;
-				// console.log( "from2to: " + from2toAngle + ", from2other: " + from2otherAngle + ", other2to: " + other2toAngle );
-
-				if ( from2toAngle > 0 ) {
-
-					// main angle < 180 deg.
-					return	( ( from2otherAngle >= 0 ) && ( other2toAngle >= 0 ) );
-
-				} else {
-
-					// main angle > 180 deg.
-					return	( ( from2otherAngle >= 0 ) || ( other2toAngle >= 0 ) );
-
-				}
-
-			} else {
-
-				// angle == 180 deg.
-				// console.log( "from2to: 180 deg., from2other: " + from2otherAngle  );
-				return	( from2otherAngle > 0 );
-
-			}
-
-		}
-
-
-		function removeHoles( contour, holes ) {
-
-			var shape = contour.concat(); // work on this shape
-			var hole;
-
-			function isCutLineInsideAngles( inShapeIdx, inHoleIdx ) {
-
-				// Check if hole point lies within angle around shape point
-				var lastShapeIdx = shape.length - 1;
-
-				var prevShapeIdx = inShapeIdx - 1;
-				if ( prevShapeIdx < 0 )			prevShapeIdx = lastShapeIdx;
-
-				var nextShapeIdx = inShapeIdx + 1;
-				if ( nextShapeIdx > lastShapeIdx )	nextShapeIdx = 0;
-
-				var insideAngle = isPointInsideAngle( shape[ inShapeIdx ], shape[ prevShapeIdx ], shape[ nextShapeIdx ], hole[ inHoleIdx ] );
-				if ( ! insideAngle ) {
-
-					// console.log( "Vertex (Shape): " + inShapeIdx + ", Point: " + hole[inHoleIdx].x + "/" + hole[inHoleIdx].y );
-					return	false;
-
-				}
-
-				// Check if shape point lies within angle around hole point
-				var lastHoleIdx = hole.length - 1;
-
-				var prevHoleIdx = inHoleIdx - 1;
-				if ( prevHoleIdx < 0 )			prevHoleIdx = lastHoleIdx;
-
-				var nextHoleIdx = inHoleIdx + 1;
-				if ( nextHoleIdx > lastHoleIdx )	nextHoleIdx = 0;
-
-				insideAngle = isPointInsideAngle( hole[ inHoleIdx ], hole[ prevHoleIdx ], hole[ nextHoleIdx ], shape[ inShapeIdx ] );
-				if ( ! insideAngle ) {
-
-					// console.log( "Vertex (Hole): " + inHoleIdx + ", Point: " + shape[inShapeIdx].x + "/" + shape[inShapeIdx].y );
-					return	false;
-
-				}
-
-				return	true;
-
-			}
-
-			function intersectsShapeEdge( inShapePt, inHolePt ) {
-
-				// checks for intersections with shape edges
-				var sIdx, nextIdx, intersection;
-				for ( sIdx = 0; sIdx < shape.length; sIdx ++ ) {
-
-					nextIdx = sIdx + 1; nextIdx %= shape.length;
-					intersection = intersect_segments_2D( inShapePt, inHolePt, shape[ sIdx ], shape[ nextIdx ], true );
-					if ( intersection.length > 0 )		return	true;
-
-				}
-
-				return	false;
+		function addContour( vertices, contour ) {
 
-			}
-
-			var indepHoles = [];
-
-			function intersectsHoleEdge( inShapePt, inHolePt ) {
-
-				// checks for intersections with hole edges
-				var ihIdx, chkHole,
-					hIdx, nextIdx, intersection;
-				for ( ihIdx = 0; ihIdx < indepHoles.length; ihIdx ++ ) {
-
-					chkHole = holes[ indepHoles[ ihIdx ] ];
-					for ( hIdx = 0; hIdx < chkHole.length; hIdx ++ ) {
-
-						nextIdx = hIdx + 1; nextIdx %= chkHole.length;
-						intersection = intersect_segments_2D( inShapePt, inHolePt, chkHole[ hIdx ], chkHole[ nextIdx ], true );
-						if ( intersection.length > 0 )		return	true;
-
-					}
-
-				}
-				return	false;
+			for ( var i = 0; i < contour.length; i ++ ) {
 
-			}
-
-			var holeIndex, shapeIndex,
-				shapePt, holePt,
-				holeIdx, cutKey, failedCuts = [],
-				tmpShape1, tmpShape2,
-				tmpHole1, tmpHole2;
-
-			for ( var h = 0, hl = holes.length; h < hl; h ++ ) {
-
-				indepHoles.push( h );
+				vertices.push( contour[ i ].x );
+				vertices.push( contour[ i ].y );
 
 			}
 
-			var minShapeIndex = 0;
-			var counter = indepHoles.length * 2;
-			while ( indepHoles.length > 0 ) {
-
-				counter --;
-				if ( counter < 0 ) {
-
-					console.log( 'THREE.ShapeUtils: Infinite Loop! Holes left:" + indepHoles.length + ", Probably Hole outside Shape!' );
-					break;
-
-				}
-
-				// search for shape-vertex and hole-vertex,
-				// which can be connected without intersections
-				for ( shapeIndex = minShapeIndex; shapeIndex < shape.length; shapeIndex ++ ) {
-
-					shapePt = shape[ shapeIndex ];
-					holeIndex	= - 1;
-
-					// search for hole which can be reached without intersections
-					for ( var h = 0; h < indepHoles.length; h ++ ) {
-
-						holeIdx = indepHoles[ h ];
-
-						// prevent multiple checks
-						cutKey = shapePt.x + ':' + shapePt.y + ':' + holeIdx;
-						if ( failedCuts[ cutKey ] !== undefined )			continue;
-
-						hole = holes[ holeIdx ];
-						for ( var h2 = 0; h2 < hole.length; h2 ++ ) {
-
-							holePt = hole[ h2 ];
-							if ( ! isCutLineInsideAngles( shapeIndex, h2 ) )		continue;
-							if ( intersectsShapeEdge( shapePt, holePt ) )		continue;
-							if ( intersectsHoleEdge( shapePt, holePt ) )		continue;
-
-							holeIndex = h2;
-							indepHoles.splice( h, 1 );
-
-							tmpShape1 = shape.slice( 0, shapeIndex + 1 );
-							tmpShape2 = shape.slice( shapeIndex );
-							tmpHole1 = hole.slice( holeIndex );
-							tmpHole2 = hole.slice( 0, holeIndex + 1 );
-
-							shape = tmpShape1.concat( tmpHole1 ).concat( tmpHole2 ).concat( tmpShape2 );
-
-							minShapeIndex = shapeIndex;
-
-							// Debug only, to show the selected cuts
-							// glob_CutLines.push( [ shapePt, holePt ] );
-
-							break;
-
-						}
-						if ( holeIndex >= 0 )	break;		// hole-vertex found
-
-						failedCuts[ cutKey ] = true;			// remember failure
-
-					}
-					if ( holeIndex >= 0 )	break;		// hole-vertex found
-
-				}
-
-			}
-
-			return shape; 			/* shape with no holes */
-
 		}
 
+		var vertices = []; // flat array of vertices like [ x0,y0, x1,y1, x2,y2, ... ]
+		var holeIndices = []; // array of hole indices
+		var faces = []; // final array of vertex indices like [ [ a,b,d ], [ b,c,d ] ]
 
-		var i, il, f, face,
-			key, index,
-			allPointsMap = {};
+		removeDupEndPts( contour );
+		addContour( vertices, contour );
 
-		// To maintain reference to old shape, one must match coordinates, or offset the indices from original arrays. It's probably easier to do the first.
+		//
 
-		var allpoints = contour.concat();
+		var holeIndex = contour.length;
+		holes.forEach( removeDupEndPts );
 
-		for ( var h = 0, hl = holes.length; h < hl; h ++ ) {
+		for ( i = 0; i < holes.length; i ++ ) {
 
-			Array.prototype.push.apply( allpoints, holes[ h ] );
+			holeIndices.push( holeIndex );
+			holeIndex += holes[ i ].length;
+			addContour( vertices, holes[ i ] );
 
 		}
 
-		//console.log( "allpoints",allpoints, allpoints.length );
-
-		// prepare all points map
-
-		for ( i = 0, il = allpoints.length; i < il; i ++ ) {
+		//
 
-			key = allpoints[ i ].x + ':' + allpoints[ i ].y;
+		var triangles = Earcut.triangulate( vertices, holeIndices );
 
-			if ( allPointsMap[ key ] !== undefined ) {
+		//
 
-				console.warn( 'THREE.ShapeUtils: Duplicate point', key, i );
+		for ( var i = 0; i < triangles.length; i += 3 ) {
 
-			}
-
-			allPointsMap[ key ] = i;
+			faces.push( triangles.slice( i, i + 3 ) );
 
 		}
 
-		// remove holes by cutting paths to holes and adding them to the shape
-		var shapeWithoutHoles = removeHoles( contour, holes );
-
-		var triangles = ShapeUtils.triangulate( shapeWithoutHoles, false ); // True returns indices for points of spooled shape
-		//console.log( "triangles",triangles, triangles.length );
-
-		// check all face vertices against all points map
-
-		for ( i = 0, il = triangles.length; i < il; i ++ ) {
-
-			face = triangles[ i ];
-
-			for ( f = 0; f < 3; f ++ ) {
-
-				key = face[ f ].x + ':' + face[ f ].y;
-
-				index = allPointsMap[ key ];
-
-				if ( index !== undefined ) {
-
-					face[ f ] = index;
-
-				}
-
-			}
-
-		}
-
-		return triangles.concat();
-
-	},
-
-	isClockWise: function ( pts ) {
-
-		return ShapeUtils.area( pts ) < 0;
+		return faces;
 
 	}
 

+ 1 - 1
src/geometries/ExtrudeGeometry.js

@@ -747,7 +747,7 @@ ExtrudeBufferGeometry.prototype.addShape = function ( shape, options ) {
 
 		this.setIndex( indicesArray );
 		this.addAttribute( 'position', new Float32BufferAttribute( verticesArray, 3 ) );
-		this.addAttribute( 'uv', new Float32BufferAttribute( options.arrays.uv, 2 ) );
+		this.addAttribute( 'uv', new Float32BufferAttribute( uvArray, 2 ) );
 
 	}
 

+ 18 - 10
src/math/Math.js

@@ -12,19 +12,27 @@ var _Math = {
 
 		// http://stackoverflow.com/questions/105034/how-to-create-a-guid-uuid-in-javascript/21963136#21963136
 
-		var lut = []; for (var i=0; i<256; i++) { lut[i] = (i<16?'0':'')+(i).toString(16).toUpperCase(); }
+		var lut = [];
+
+		for ( var i = 0; i < 256; i ++ ) {
+
+			lut[ i ] = ( i < 16 ? '0' : '' ) + ( i ).toString( 16 ).toUpperCase();
 
-		return function () {
-			var d0 = Math.random()*0xffffffff|0;
-			var d1 = Math.random()*0xffffffff|0;
-			var d2 = Math.random()*0xffffffff|0;
-			var d3 = Math.random()*0xffffffff|0;
-			return lut[d0&0xff]+lut[d0>>8&0xff]+lut[d0>>16&0xff]+lut[d0>>24&0xff]+'-'+
-				lut[d1&0xff]+lut[d1>>8&0xff]+'-'+lut[d1>>16&0x0f|0x40]+lut[d1>>24&0xff]+'-'+
-				lut[d2&0x3f|0x80]+lut[d2>>8&0xff]+'-'+lut[d2>>16&0xff]+lut[d2>>24&0xff]+
-				lut[d3&0xff]+lut[d3>>8&0xff]+lut[d3>>16&0xff]+lut[d3>>24&0xff];
 		}
 
+		return function () {
+
+			var d0 = Math.random() * 0xffffffff | 0;
+			var d1 = Math.random() * 0xffffffff | 0;
+			var d2 = Math.random() * 0xffffffff | 0;
+			var d3 = Math.random() * 0xffffffff | 0;
+			return lut[ d0 & 0xff ] + lut[ d0 >> 8 & 0xff ] + lut[ d0 >> 16 & 0xff ] + lut[ d0 >> 24 & 0xff ] + '-' +
+				lut[ d1 & 0xff ] + lut[ d1 >> 8 & 0xff ] + '-' + lut[ d1 >> 16 & 0x0f | 0x40 ] + lut[ d1 >> 24 & 0xff ] + '-' +
+				lut[ d2 & 0x3f | 0x80 ] + lut[ d2 >> 8 & 0xff ] + '-' + lut[ d2 >> 16 & 0xff ] + lut[ d2 >> 24 & 0xff ] +
+				lut[ d3 & 0xff ] + lut[ d3 >> 8 & 0xff ] + lut[ d3 >> 16 & 0xff ] + lut[ d3 >> 24 & 0xff ];
+
+		};
+
 	} )(),
 
 	clamp: function ( value, min, max ) {

+ 3 - 1
src/objects/Group.js

@@ -14,7 +14,9 @@ function Group() {
 
 Group.prototype = Object.assign( Object.create( Object3D.prototype ), {
 
-	constructor: Group
+	constructor: Group,
+
+	isGroup: true
 
 } );
 

+ 13 - 13
src/renderers/webgl/WebGLProgram.js

@@ -37,14 +37,14 @@ function getEncodingComponents( encoding ) {
 function getTexelDecodingFunction( functionName, encoding ) {
 
 	var components = getEncodingComponents( encoding );
-	return "vec4 " + functionName + "( vec4 value ) { return " + components[ 0 ] + "ToLinear" + components[ 1 ] + "; }";
+	return 'vec4 ' + functionName + '( vec4 value ) { return ' + components[ 0 ] + 'ToLinear' + components[ 1 ] + '; }';
 
 }
 
 function getTexelEncodingFunction( functionName, encoding ) {
 
 	var components = getEncodingComponents( encoding );
-	return "vec4 " + functionName + "( vec4 value ) { return LinearTo" + components[ 0 ] + components[ 1 ] + "; }";
+	return 'vec4 ' + functionName + '( vec4 value ) { return LinearTo' + components[ 0 ] + components[ 1 ] + '; }';
 
 }
 
@@ -55,19 +55,19 @@ function getToneMappingFunction( functionName, toneMapping ) {
 	switch ( toneMapping ) {
 
 		case LinearToneMapping:
-			toneMappingName = "Linear";
+			toneMappingName = 'Linear';
 			break;
 
 		case ReinhardToneMapping:
-			toneMappingName = "Reinhard";
+			toneMappingName = 'Reinhard';
 			break;
 
 		case Uncharted2ToneMapping:
-			toneMappingName = "Uncharted2";
+			toneMappingName = 'Uncharted2';
 			break;
 
 		case CineonToneMapping:
-			toneMappingName = "OptimizedCineon";
+			toneMappingName = 'OptimizedCineon';
 			break;
 
 		default:
@@ -75,7 +75,7 @@ function getToneMappingFunction( functionName, toneMapping ) {
 
 	}
 
-	return "vec3 " + functionName + "( vec3 color ) { return " + toneMappingName + "ToneMapping( color ); }";
+	return 'vec3 ' + functionName + '( vec3 color ) { return ' + toneMappingName + 'ToneMapping( color ); }';
 
 }
 
@@ -468,9 +468,9 @@ function WebGLProgram( renderer, extensions, code, material, shader, parameters
 			parameters.shadowMapEnabled ? '#define USE_SHADOWMAP' : '',
 			parameters.shadowMapEnabled ? '#define ' + shadowMapTypeDefine : '',
 
-			parameters.premultipliedAlpha ? "#define PREMULTIPLIED_ALPHA" : '',
+			parameters.premultipliedAlpha ? '#define PREMULTIPLIED_ALPHA' : '',
 
-			parameters.physicallyCorrectLights ? "#define PHYSICALLY_CORRECT_LIGHTS" : '',
+			parameters.physicallyCorrectLights ? '#define PHYSICALLY_CORRECT_LIGHTS' : '',
 
 			parameters.logarithmicDepthBuffer ? '#define USE_LOGDEPTHBUF' : '',
 			parameters.logarithmicDepthBuffer && extensions.get( 'EXT_frag_depth' ) ? '#define USE_LOGDEPTHBUF_EXT' : '',
@@ -480,9 +480,9 @@ function WebGLProgram( renderer, extensions, code, material, shader, parameters
 			'uniform mat4 viewMatrix;',
 			'uniform vec3 cameraPosition;',
 
-			( parameters.toneMapping !== NoToneMapping ) ? "#define TONE_MAPPING" : '',
+			( parameters.toneMapping !== NoToneMapping ) ? '#define TONE_MAPPING' : '',
 			( parameters.toneMapping !== NoToneMapping ) ? ShaderChunk[ 'tonemapping_pars_fragment' ] : '', // this code is required here because it is used by the toneMapping() function defined below
-			( parameters.toneMapping !== NoToneMapping ) ? getToneMappingFunction( "toneMapping", parameters.toneMapping ) : '',
+			( parameters.toneMapping !== NoToneMapping ) ? getToneMappingFunction( 'toneMapping', parameters.toneMapping ) : '',
 
 			parameters.dithering ? '#define DITHERING' : '',
 
@@ -490,9 +490,9 @@ function WebGLProgram( renderer, extensions, code, material, shader, parameters
 			parameters.mapEncoding ? getTexelDecodingFunction( 'mapTexelToLinear', parameters.mapEncoding ) : '',
 			parameters.envMapEncoding ? getTexelDecodingFunction( 'envMapTexelToLinear', parameters.envMapEncoding ) : '',
 			parameters.emissiveMapEncoding ? getTexelDecodingFunction( 'emissiveMapTexelToLinear', parameters.emissiveMapEncoding ) : '',
-			parameters.outputEncoding ? getTexelEncodingFunction( "linearToOutputTexel", parameters.outputEncoding ) : '',
+			parameters.outputEncoding ? getTexelEncodingFunction( 'linearToOutputTexel', parameters.outputEncoding ) : '',
 
-			parameters.depthPacking ? "#define DEPTH_PACKING " + material.depthPacking : '',
+			parameters.depthPacking ? '#define DEPTH_PACKING ' + material.depthPacking : '',
 
 			'\n'
 

+ 0 - 29
src/renderers/webvr/WebVRManager.js

@@ -24,9 +24,6 @@ function WebVRManager( renderer ) {
 
 	var matrixWorldInverse = new Matrix4();
 
-	var standingMatrix = new Matrix4();
-	var standingMatrixInverse = new Matrix4();
-
 	var cameraL = new PerspectiveCamera();
 	cameraL.bounds = new Vector4( 0.0, 0.0, 0.5, 1.0 );
 	cameraL.layers.enable( 1 );
@@ -73,7 +70,6 @@ function WebVRManager( renderer ) {
 	//
 
 	this.enabled = false;
-	this.standing = false;
 
 	this.getDevice = function () {
 
@@ -135,18 +131,6 @@ function WebVRManager( renderer ) {
 
 		poseObject.updateMatrixWorld();
 
-		var stageParameters = device.stageParameters;
-
-		if ( this.standing && stageParameters ) {
-
-			standingMatrix.fromArray( stageParameters.sittingToStandingTransform );
-			standingMatrixInverse.getInverse( standingMatrix );
-
-			camera.matrixWorld.multiply( standingMatrix );
-			camera.matrixWorldInverse.multiply( standingMatrixInverse );
-
-		}
-
 		if ( device.isPresenting === false ) return camera;
 
 		//
@@ -163,13 +147,6 @@ function WebVRManager( renderer ) {
 		cameraL.matrixWorldInverse.fromArray( frameData.leftViewMatrix );
 		cameraR.matrixWorldInverse.fromArray( frameData.rightViewMatrix );
 
-		if ( this.standing && stageParameters ) {
-
-			cameraL.matrixWorldInverse.multiply( standingMatrixInverse );
-			cameraR.matrixWorldInverse.multiply( standingMatrixInverse );
-
-		}
-
 		var parent = camera.parent;
 
 		if ( parent !== null ) {
@@ -220,12 +197,6 @@ function WebVRManager( renderer ) {
 
 	};
 
-	this.getStandingMatrix = function () {
-
-		return standingMatrix;
-
-	};
-
 	this.submitFrame = function () {
 
 		if ( device && device.isPresenting ) device.submitFrame();

+ 0 - 6
test/unit/src/renderers/webvr/WebVRManager.tests.js

@@ -37,12 +37,6 @@ export default QUnit.module( 'Renderers', () => {
 
 			} );
 
-			QUnit.test( "getStandingMatrix", ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
 			QUnit.test( "submitFrame", ( assert ) => {
 
 				assert.ok( false, "everything's gonna be alright" );

+ 0 - 9435
test/unit/three.editor.unit.js

@@ -1,9435 +0,0 @@
-(function (global, factory) {
-	typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
-	typeof define === 'function' && define.amd ? define(factory) :
-	(factory());
-}(this, (function () { 'use strict';
-
-	QUnit.module( "Editor", () => {
-
-	/**
-	 * @author dforrer / https://github.com/dforrer
-	 * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
-	 */
-
-	/**
-	 * @param editorRef pointer to main editor object used to initialize
-	 *        each command object with a reference to the editor
-	 * @constructor
-	 */
-
-	var Command$1 = function ( editorRef ) {
-
-		this.id = - 1;
-		this.inMemory = false;
-		this.updatable = false;
-		this.type = '';
-		this.name = '';
-
-		if ( editorRef !== undefined ) {
-
-			Command$1.editor = editorRef;
-
-		}
-		this.editor = Command$1.editor;
-
-
-	};
-
-	Command$1.prototype.toJSON = function () {
-
-		var output = {};
-		output.type = this.type;
-		output.id = this.id;
-		output.name = this.name;
-		return output;
-
-	};
-
-	Command$1.prototype.fromJSON = function ( json ) {
-
-		this.inMemory = true;
-		this.type = json.type;
-		this.id = json.id;
-		this.name = json.name;
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Command', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Config', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	var Editor = function () {
-
-		this.DEFAULT_CAMERA = new THREE.PerspectiveCamera( 50, 1, 0.1, 10000 );
-		this.DEFAULT_CAMERA.name = 'Camera';
-		this.DEFAULT_CAMERA.position.set( 20, 10, 20 );
-		this.DEFAULT_CAMERA.lookAt( new THREE.Vector3() );
-
-		var Signal = signals.Signal;
-
-		this.signals = {
-
-			// script
-
-			editScript: new Signal(),
-
-			// player
-
-			startPlayer: new Signal(),
-			stopPlayer: new Signal(),
-
-			// actions
-
-			showModal: new Signal(),
-
-			// notifications
-
-			editorCleared: new Signal(),
-
-			savingStarted: new Signal(),
-			savingFinished: new Signal(),
-
-			themeChanged: new Signal(),
-
-			transformModeChanged: new Signal(),
-			snapChanged: new Signal(),
-			spaceChanged: new Signal(),
-			rendererChanged: new Signal(),
-
-			sceneBackgroundChanged: new Signal(),
-			sceneFogChanged: new Signal(),
-			sceneGraphChanged: new Signal(),
-
-			cameraChanged: new Signal(),
-
-			geometryChanged: new Signal(),
-
-			objectSelected: new Signal(),
-			objectFocused: new Signal(),
-
-			objectAdded: new Signal(),
-			objectChanged: new Signal(),
-			objectRemoved: new Signal(),
-
-			helperAdded: new Signal(),
-			helperRemoved: new Signal(),
-
-			materialChanged: new Signal(),
-
-			scriptAdded: new Signal(),
-			scriptChanged: new Signal(),
-			scriptRemoved: new Signal(),
-
-			windowResize: new Signal(),
-
-			showGridChanged: new Signal(),
-			refreshSidebarObject3D: new Signal(),
-			historyChanged: new Signal()
-
-		};
-
-		this.config = new Config( 'threejs-editor' );
-		this.history = new History( this );
-		this.storage = new Storage();
-		this.loader = new Loader( this );
-
-		this.camera = this.DEFAULT_CAMERA.clone();
-
-		this.scene = new THREE.Scene();
-		this.scene.name = 'Scene';
-		this.scene.background = new THREE.Color( 0xaaaaaa );
-
-		this.sceneHelpers = new THREE.Scene();
-
-		this.object = {};
-		this.geometries = {};
-		this.materials = {};
-		this.textures = {};
-		this.scripts = {};
-
-		this.selected = null;
-		this.helpers = {};
-
-	};
-
-	Editor.prototype = {
-
-		setTheme: function ( value ) {
-
-			document.getElementById( 'theme' ).href = value;
-
-			this.signals.themeChanged.dispatch( value );
-
-		},
-
-		//
-
-		setScene: function ( scene ) {
-
-			this.scene.uuid = scene.uuid;
-			this.scene.name = scene.name;
-
-			if ( scene.background !== null ) this.scene.background = scene.background.clone();
-			if ( scene.fog !== null ) this.scene.fog = scene.fog.clone();
-
-			this.scene.userData = JSON.parse( JSON.stringify( scene.userData ) );
-
-			// avoid render per object
-
-			this.signals.sceneGraphChanged.active = false;
-
-			while ( scene.children.length > 0 ) {
-
-				this.addObject( scene.children[ 0 ] );
-
-			}
-
-			this.signals.sceneGraphChanged.active = true;
-			this.signals.sceneGraphChanged.dispatch();
-
-		},
-
-		//
-
-		addObject: function ( object ) {
-
-			var scope = this;
-
-			object.traverse( function ( child ) {
-
-				if ( child.geometry !== undefined ) scope.addGeometry( child.geometry );
-				if ( child.material !== undefined ) scope.addMaterial( child.material );
-
-				scope.addHelper( child );
-
-			} );
-
-			this.scene.add( object );
-
-			this.signals.objectAdded.dispatch( object );
-			this.signals.sceneGraphChanged.dispatch();
-
-		},
-
-		moveObject: function ( object, parent, before ) {
-
-			if ( parent === undefined ) {
-
-				parent = this.scene;
-
-			}
-
-			parent.add( object );
-
-			// sort children array
-
-			if ( before !== undefined ) {
-
-				var index = parent.children.indexOf( before );
-				parent.children.splice( index, 0, object );
-				parent.children.pop();
-
-			}
-
-			this.signals.sceneGraphChanged.dispatch();
-
-		},
-
-		nameObject: function ( object, name ) {
-
-			object.name = name;
-			this.signals.sceneGraphChanged.dispatch();
-
-		},
-
-		removeObject: function ( object ) {
-
-			if ( object.parent === null ) return; // avoid deleting the camera or scene
-
-			var scope = this;
-
-			object.traverse( function ( child ) {
-
-				scope.removeHelper( child );
-
-			} );
-
-			object.parent.remove( object );
-
-			this.signals.objectRemoved.dispatch( object );
-			this.signals.sceneGraphChanged.dispatch();
-
-		},
-
-		addGeometry: function ( geometry ) {
-
-			this.geometries[ geometry.uuid ] = geometry;
-
-		},
-
-		setGeometryName: function ( geometry, name ) {
-
-			geometry.name = name;
-			this.signals.sceneGraphChanged.dispatch();
-
-		},
-
-		addMaterial: function ( material ) {
-
-			this.materials[ material.uuid ] = material;
-
-		},
-
-		setMaterialName: function ( material, name ) {
-
-			material.name = name;
-			this.signals.sceneGraphChanged.dispatch();
-
-		},
-
-		addTexture: function ( texture ) {
-
-			this.textures[ texture.uuid ] = texture;
-
-		},
-
-		//
-
-		addHelper: function () {
-
-			var geometry = new THREE.SphereBufferGeometry( 2, 4, 2 );
-			var material = new THREE.MeshBasicMaterial( { color: 0xff0000, visible: false } );
-
-			return function ( object ) {
-
-				var helper;
-
-				if ( object instanceof THREE.Camera ) {
-
-					helper = new THREE.CameraHelper( object, 1 );
-
-				} else if ( object instanceof THREE.PointLight ) {
-
-					helper = new THREE.PointLightHelper( object, 1 );
-
-				} else if ( object instanceof THREE.DirectionalLight ) {
-
-					helper = new THREE.DirectionalLightHelper( object, 1 );
-
-				} else if ( object instanceof THREE.SpotLight ) {
-
-					helper = new THREE.SpotLightHelper( object, 1 );
-
-				} else if ( object instanceof THREE.HemisphereLight ) {
-
-					helper = new THREE.HemisphereLightHelper( object, 1 );
-
-				} else if ( object instanceof THREE.SkinnedMesh ) {
-
-					helper = new THREE.SkeletonHelper( object );
-
-				} else {
-
-					// no helper for this object type
-					return;
-
-				}
-
-				var picker = new THREE.Mesh( geometry, material );
-				picker.name = 'picker';
-				picker.userData.object = object;
-				helper.add( picker );
-
-				this.sceneHelpers.add( helper );
-				this.helpers[ object.id ] = helper;
-
-				this.signals.helperAdded.dispatch( helper );
-
-			};
-
-		}(),
-
-		removeHelper: function ( object ) {
-
-			if ( this.helpers[ object.id ] !== undefined ) {
-
-				var helper = this.helpers[ object.id ];
-				helper.parent.remove( helper );
-
-				delete this.helpers[ object.id ];
-
-				this.signals.helperRemoved.dispatch( helper );
-
-			}
-
-		},
-
-		//
-
-		addScript: function ( object, script ) {
-
-			if ( this.scripts[ object.uuid ] === undefined ) {
-
-				this.scripts[ object.uuid ] = [];
-
-			}
-
-			this.scripts[ object.uuid ].push( script );
-
-			this.signals.scriptAdded.dispatch( script );
-
-		},
-
-		removeScript: function ( object, script ) {
-
-			if ( this.scripts[ object.uuid ] === undefined ) return;
-
-			var index = this.scripts[ object.uuid ].indexOf( script );
-
-			if ( index !== - 1 ) {
-
-				this.scripts[ object.uuid ].splice( index, 1 );
-
-			}
-
-			this.signals.scriptRemoved.dispatch( script );
-
-		},
-
-		getObjectMaterial: function ( object, slot ) {
-
-			var material = object.material;
-
-			if ( Array.isArray( material ) ) {
-
-				material = material[ slot ];
-
-			}
-
-			return material;
-
-		},
-
-		setObjectMaterial: function ( object, slot, newMaterial ) {
-
-			if ( Array.isArray( object.material ) ) {
-
-				object.material[ slot ] = newMaterial;
-
-			} else {
-
-				object.material = newMaterial;
-
-			}
-
-		},
-
-		//
-
-		select: function ( object ) {
-
-			if ( this.selected === object ) return;
-
-			var uuid = null;
-
-			if ( object !== null ) {
-
-				uuid = object.uuid;
-
-			}
-
-			this.selected = object;
-
-			this.config.setKey( 'selected', uuid );
-			this.signals.objectSelected.dispatch( object );
-
-		},
-
-		selectById: function ( id ) {
-
-			if ( id === this.camera.id ) {
-
-				this.select( this.camera );
-				return;
-
-			}
-
-			this.select( this.scene.getObjectById( id, true ) );
-
-		},
-
-		selectByUuid: function ( uuid ) {
-
-			var scope = this;
-
-			this.scene.traverse( function ( child ) {
-
-				if ( child.uuid === uuid ) {
-
-					scope.select( child );
-
-				}
-
-			} );
-
-		},
-
-		deselect: function () {
-
-			this.select( null );
-
-		},
-
-		focus: function ( object ) {
-
-			this.signals.objectFocused.dispatch( object );
-
-		},
-
-		focusById: function ( id ) {
-
-			this.focus( this.scene.getObjectById( id, true ) );
-
-		},
-
-		clear: function () {
-
-			this.history.clear();
-			this.storage.clear();
-
-			this.camera.copy( this.DEFAULT_CAMERA );
-			this.scene.background.setHex( 0xaaaaaa );
-			this.scene.fog = null;
-
-			var objects = this.scene.children;
-
-			while ( objects.length > 0 ) {
-
-				this.removeObject( objects[ 0 ] );
-
-			}
-
-			this.geometries = {};
-			this.materials = {};
-			this.textures = {};
-			this.scripts = {};
-
-			this.deselect();
-
-			this.signals.editorCleared.dispatch();
-
-		},
-
-		//
-
-		fromJSON: function ( json ) {
-
-			var loader = new THREE.ObjectLoader();
-
-			// backwards
-
-			if ( json.scene === undefined ) {
-
-				this.setScene( loader.parse( json ) );
-				return;
-
-			}
-
-			var camera = loader.parse( json.camera );
-
-			this.camera.copy( camera );
-			this.camera.aspect = this.DEFAULT_CAMERA.aspect;
-			this.camera.updateProjectionMatrix();
-
-			this.history.fromJSON( json.history );
-			this.scripts = json.scripts;
-
-			this.setScene( loader.parse( json.scene ) );
-
-		},
-
-		toJSON: function () {
-
-			// scripts clean up
-
-			var scene = this.scene;
-			var scripts = this.scripts;
-
-			for ( var key in scripts ) {
-
-				var script = scripts[ key ];
-
-				if ( script.length === 0 || scene.getObjectByProperty( 'uuid', key ) === undefined ) {
-
-					delete scripts[ key ];
-
-				}
-
-			}
-
-			//
-
-			return {
-
-				metadata: {},
-				project: {
-					gammaInput: this.config.getKey( 'project/renderer/gammaInput' ),
-					gammaOutput: this.config.getKey( 'project/renderer/gammaOutput' ),
-					shadows: this.config.getKey( 'project/renderer/shadows' ),
-					vr: this.config.getKey( 'project/vr' )
-				},
-				camera: this.camera.toJSON(),
-				scene: this.scene.toJSON(),
-				scripts: this.scripts,
-				history: this.history.toJSON()
-
-			};
-
-		},
-
-		objectByUuid: function ( uuid ) {
-
-			return this.scene.getObjectByProperty( 'uuid', uuid, true );
-
-		},
-
-		execute: function ( cmd, optionalName ) {
-
-			this.history.execute( cmd, optionalName );
-
-		},
-
-		undo: function () {
-
-			this.history.undo();
-
-		},
-
-		redo: function () {
-
-			this.history.redo();
-
-		}
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Editor', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author dforrer / https://github.com/dforrer
-	 * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
-	 */
-
-	History = function ( editor ) {
-
-		this.editor = editor;
-		this.undos = [];
-		this.redos = [];
-		this.lastCmdTime = new Date();
-		this.idCounter = 0;
-
-		this.historyDisabled = false;
-		this.config = editor.config;
-
-		//Set editor-reference in Command
-
-		Command( editor );
-
-		// signals
-
-		var scope = this;
-
-		this.editor.signals.startPlayer.add( function () {
-
-			scope.historyDisabled = true;
-
-		} );
-
-		this.editor.signals.stopPlayer.add( function () {
-
-			scope.historyDisabled = false;
-
-		} );
-
-	};
-
-	History.prototype = {
-
-		execute: function ( cmd, optionalName ) {
-
-			var lastCmd = this.undos[ this.undos.length - 1 ];
-			var timeDifference = new Date().getTime() - this.lastCmdTime.getTime();
-
-			var isUpdatableCmd = lastCmd &&
-				lastCmd.updatable &&
-				cmd.updatable &&
-				lastCmd.object === cmd.object &&
-				lastCmd.type === cmd.type &&
-				lastCmd.script === cmd.script &&
-				lastCmd.attributeName === cmd.attributeName;
-
-			if ( isUpdatableCmd && cmd.type === "SetScriptValueCommand" ) {
-
-				// When the cmd.type is "SetScriptValueCommand" the timeDifference is ignored
-
-				lastCmd.update( cmd );
-				cmd = lastCmd;
-
-			} else if ( isUpdatableCmd && timeDifference < 500 ) {
-
-				lastCmd.update( cmd );
-				cmd = lastCmd;
-
-			} else {
-
-				// the command is not updatable and is added as a new part of the history
-
-				this.undos.push( cmd );
-				cmd.id = ++ this.idCounter;
-
-			}
-			cmd.name = ( optionalName !== undefined ) ? optionalName : cmd.name;
-			cmd.execute();
-			cmd.inMemory = true;
-
-			if ( this.config.getKey( 'settings/history' ) ) {
-
-				cmd.json = cmd.toJSON();	// serialize the cmd immediately after execution and append the json to the cmd
-
-			}
-			this.lastCmdTime = new Date();
-
-			// clearing all the redo-commands
-
-			this.redos = [];
-			this.editor.signals.historyChanged.dispatch( cmd );
-
-		},
-
-		undo: function () {
-
-			if ( this.historyDisabled ) {
-
-				alert( "Undo/Redo disabled while scene is playing." );
-				return;
-
-			}
-
-			var cmd = undefined;
-
-			if ( this.undos.length > 0 ) {
-
-				cmd = this.undos.pop();
-
-				if ( cmd.inMemory === false ) {
-
-					cmd.fromJSON( cmd.json );
-
-				}
-
-			}
-
-			if ( cmd !== undefined ) {
-
-				cmd.undo();
-				this.redos.push( cmd );
-				this.editor.signals.historyChanged.dispatch( cmd );
-
-			}
-
-			return cmd;
-
-		},
-
-		redo: function () {
-
-			if ( this.historyDisabled ) {
-
-				alert( "Undo/Redo disabled while scene is playing." );
-				return;
-
-			}
-
-			var cmd = undefined;
-
-			if ( this.redos.length > 0 ) {
-
-				cmd = this.redos.pop();
-
-				if ( cmd.inMemory === false ) {
-
-					cmd.fromJSON( cmd.json );
-
-				}
-
-			}
-
-			if ( cmd !== undefined ) {
-
-				cmd.execute();
-				this.undos.push( cmd );
-				this.editor.signals.historyChanged.dispatch( cmd );
-
-			}
-
-			return cmd;
-
-		},
-
-		toJSON: function () {
-
-			var history = {};
-			history.undos = [];
-			history.redos = [];
-
-			if ( ! this.config.getKey( 'settings/history' ) ) {
-
-				return history;
-
-			}
-
-			// Append Undos to History
-
-			for ( var i = 0 ; i < this.undos.length; i ++ ) {
-
-				if ( this.undos[ i ].hasOwnProperty( "json" ) ) {
-
-					history.undos.push( this.undos[ i ].json );
-
-				}
-
-			}
-
-			// Append Redos to History
-
-			for ( var i = 0 ; i < this.redos.length; i ++ ) {
-
-				if ( this.redos[ i ].hasOwnProperty( "json" ) ) {
-
-					history.redos.push( this.redos[ i ].json );
-
-				}
-
-			}
-
-			return history;
-
-		},
-
-		fromJSON: function ( json ) {
-
-			if ( json === undefined ) return;
-
-			for ( var i = 0; i < json.undos.length; i ++ ) {
-
-				var cmdJSON = json.undos[ i ];
-				var cmd = new window[ cmdJSON.type ]();	// creates a new object of type "json.type"
-				cmd.json = cmdJSON;
-				cmd.id = cmdJSON.id;
-				cmd.name = cmdJSON.name;
-				this.undos.push( cmd );
-				this.idCounter = ( cmdJSON.id > this.idCounter ) ? cmdJSON.id : this.idCounter; // set last used idCounter
-
-			}
-
-			for ( var i = 0; i < json.redos.length; i ++ ) {
-
-				var cmdJSON = json.redos[ i ];
-				var cmd = new window[ cmdJSON.type ]();	// creates a new object of type "json.type"
-				cmd.json = cmdJSON;
-				cmd.id = cmdJSON.id;
-				cmd.name = cmdJSON.name;
-				this.redos.push( cmd );
-				this.idCounter = ( cmdJSON.id > this.idCounter ) ? cmdJSON.id : this.idCounter; // set last used idCounter
-
-			}
-
-			// Select the last executed undo-command
-			this.editor.signals.historyChanged.dispatch( this.undos[ this.undos.length - 1 ] );
-
-		},
-
-		clear: function () {
-
-			this.undos = [];
-			this.redos = [];
-			this.idCounter = 0;
-
-			this.editor.signals.historyChanged.dispatch();
-
-		},
-
-		goToState: function ( id ) {
-
-			if ( this.historyDisabled ) {
-
-				alert( "Undo/Redo disabled while scene is playing." );
-				return;
-
-			}
-
-			this.editor.signals.sceneGraphChanged.active = false;
-			this.editor.signals.historyChanged.active = false;
-
-			var cmd = this.undos.length > 0 ? this.undos[ this.undos.length - 1 ] : undefined;	// next cmd to pop
-
-			if ( cmd === undefined || id > cmd.id ) {
-
-				cmd = this.redo();
-				while ( cmd !== undefined && id > cmd.id ) {
-
-					cmd = this.redo();
-
-				}
-
-			} else {
-
-				while ( true ) {
-
-					cmd = this.undos[ this.undos.length - 1 ];	// next cmd to pop
-
-					if ( cmd === undefined || id === cmd.id ) break;
-
-					this.undo();
-
-				}
-
-			}
-
-			this.editor.signals.sceneGraphChanged.active = true;
-			this.editor.signals.historyChanged.active = true;
-
-			this.editor.signals.sceneGraphChanged.dispatch();
-			this.editor.signals.historyChanged.dispatch( cmd );
-
-		},
-
-		enableSerialization: function ( id ) {
-
-			/**
-			 * because there might be commands in this.undos and this.redos
-			 * which have not been serialized with .toJSON() we go back
-			 * to the oldest command and redo one command after the other
-			 * while also calling .toJSON() on them.
-			 */
-
-			this.goToState( - 1 );
-
-			this.editor.signals.sceneGraphChanged.active = false;
-			this.editor.signals.historyChanged.active = false;
-
-			var cmd = this.redo();
-			while ( cmd !== undefined ) {
-
-				if ( ! cmd.hasOwnProperty( "json" ) ) {
-
-					cmd.json = cmd.toJSON();
-
-				}
-				cmd = this.redo();
-
-			}
-
-			this.editor.signals.sceneGraphChanged.active = true;
-			this.editor.signals.historyChanged.active = true;
-
-			this.goToState( id );
-
-		}
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'History', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Loader', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Player', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Script', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Menubar', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Menubar.Add = function ( editor ) {
-
-		var container = new UI.Panel();
-		container.setClass( 'menu' );
-
-		var title = new UI.Panel();
-		title.setClass( 'title' );
-		title.setTextContent( 'Add' );
-		container.add( title );
-
-		var options = new UI.Panel();
-		options.setClass( 'options' );
-		container.add( options );
-
-		//
-
-		var meshCount = 0;
-		var lightCount = 0;
-		var cameraCount = 0;
-
-		editor.signals.editorCleared.add( function () {
-
-			meshCount = 0;
-			lightCount = 0;
-			cameraCount = 0;
-
-		} );
-
-		// Group
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'Group' );
-		option.onClick( function () {
-
-			var mesh = new THREE.Group();
-			mesh.name = 'Group ' + ( ++ meshCount );
-
-			editor.execute( new AddObjectCommand( mesh ) );
-
-		} );
-		options.add( option );
-
-		//
-
-		options.add( new UI.HorizontalRule() );
-
-		// Plane
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'Plane' );
-		option.onClick( function () {
-
-			var geometry = new THREE.PlaneBufferGeometry( 1, 1, 1, 1 );
-			var material = new THREE.MeshStandardMaterial();
-			var mesh = new THREE.Mesh( geometry, material );
-			mesh.name = 'Plane ' + ( ++ meshCount );
-
-			editor.execute( new AddObjectCommand( mesh ) );
-
-		} );
-		options.add( option );
-
-		// Box
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'Box' );
-		option.onClick( function () {
-
-			var geometry = new THREE.BoxBufferGeometry( 1, 1, 1, 1, 1, 1 );
-			var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial() );
-			mesh.name = 'Box ' + ( ++ meshCount );
-
-			editor.execute( new AddObjectCommand( mesh ) );
-
-		} );
-		options.add( option );
-
-		// Circle
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'Circle' );
-		option.onClick( function () {
-
-			var geometry = new THREE.CircleBufferGeometry( 1, 8, 0, Math.PI * 2 );
-			var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial() );
-			mesh.name = 'Circle ' + ( ++ meshCount );
-
-			editor.execute( new AddObjectCommand( mesh ) );
-
-		} );
-		options.add( option );
-
-		// Cylinder
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'Cylinder' );
-		option.onClick( function () {
-
-			var geometry = new THREE.CylinderBufferGeometry( 1, 1, 1, 8, 1, false, 0, Math.PI * 2 );
-			var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial() );
-			mesh.name = 'Cylinder ' + ( ++ meshCount );
-
-			editor.execute( new AddObjectCommand( mesh ) );
-
-		} );
-		options.add( option );
-
-		// Sphere
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'Sphere' );
-		option.onClick( function () {
-
-			var geometry = new THREE.SphereBufferGeometry( 1, 8, 6, 0, Math.PI * 2, 0, Math.PI );
-			var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial() );
-			mesh.name = 'Sphere ' + ( ++ meshCount );
-
-			editor.execute( new AddObjectCommand( mesh ) );
-
-		} );
-		options.add( option );
-
-		// Icosahedron
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'Icosahedron' );
-		option.onClick( function () {
-
-			var geometry = new THREE.IcosahedronGeometry( 1, 0 );
-			var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial() );
-			mesh.name = 'Icosahedron ' + ( ++ meshCount );
-
-			editor.execute( new AddObjectCommand( mesh ) );
-
-		} );
-		options.add( option );
-
-		// Torus
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'Torus' );
-		option.onClick( function () {
-
-			var geometry = new THREE.TorusBufferGeometry( 1, 0.4, 8, 6, Math.PI * 2 );
-			var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial() );
-			mesh.name = 'Torus ' + ( ++ meshCount );
-
-			editor.execute( new AddObjectCommand( mesh ) );
-
-		} );
-		options.add( option );
-
-		// TorusKnot
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'TorusKnot' );
-		option.onClick( function () {
-
-			var geometry = new THREE.TorusKnotBufferGeometry( 1, 0.4, 64, 8, 2, 3 );
-			var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial() );
-			mesh.name = 'TorusKnot ' + ( ++ meshCount );
-
-			editor.execute( new AddObjectCommand( mesh ) );
-
-		} );
-		options.add( option );
-
-		/*
-		// Teapot
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'Teapot' );
-		option.onClick( function () {
-
-			var size = 50;
-			var segments = 10;
-			var bottom = true;
-			var lid = true;
-			var body = true;
-			var fitLid = false;
-			var blinnScale = true;
-
-			var material = new THREE.MeshStandardMaterial();
-
-			var geometry = new THREE.TeapotBufferGeometry( size, segments, bottom, lid, body, fitLid, blinnScale );
-			var mesh = new THREE.Mesh( geometry, material );
-			mesh.name = 'Teapot ' + ( ++ meshCount );
-
-			editor.addObject( mesh );
-			editor.select( mesh );
-
-		} );
-		options.add( option );
-		*/
-
-		// Lathe
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'Lathe' );
-		option.onClick( function() {
-
-			var points = [
-				new THREE.Vector2( 0, 0 ),
-				new THREE.Vector2( 0.4, 0 ),
-				new THREE.Vector2( 0.35, 0.05 ),
-				new THREE.Vector2( 0.1, 0.075 ),
-				new THREE.Vector2( 0.08, 0.1 ),
-				new THREE.Vector2( 0.08, 0.4 ),
-				new THREE.Vector2( 0.1, 0.42 ),
-				new THREE.Vector2( 0.14, 0.48 ),
-				new THREE.Vector2( 0.2, 0.5 ),
-				new THREE.Vector2( 0.25, 0.54 ),
-				new THREE.Vector2( 0.3, 1.2 )
-			];
-
-			var geometry = new THREE.LatheBufferGeometry( points, 12, 0, Math.PI * 2 );
-			var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial( { side: THREE.DoubleSide } ) );
-			mesh.name = 'Lathe ' + ( ++ meshCount );
-
-			editor.execute( new AddObjectCommand( mesh ) );
-
-		} );
-		options.add( option );
-
-		// Sprite
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'Sprite' );
-		option.onClick( function () {
-
-			var sprite = new THREE.Sprite( new THREE.SpriteMaterial() );
-			sprite.name = 'Sprite ' + ( ++ meshCount );
-
-			editor.execute( new AddObjectCommand( sprite ) );
-
-		} );
-		options.add( option );
-
-		//
-
-		options.add( new UI.HorizontalRule() );
-
-		// PointLight
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'PointLight' );
-		option.onClick( function () {
-
-			var color = 0xffffff;
-			var intensity = 1;
-			var distance = 0;
-
-			var light = new THREE.PointLight( color, intensity, distance );
-			light.name = 'PointLight ' + ( ++ lightCount );
-
-			editor.execute( new AddObjectCommand( light ) );
-
-		} );
-		options.add( option );
-
-		// SpotLight
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'SpotLight' );
-		option.onClick( function () {
-
-			var color = 0xffffff;
-			var intensity = 1;
-			var distance = 0;
-			var angle = Math.PI * 0.1;
-			var penumbra = 0;
-
-			var light = new THREE.SpotLight( color, intensity, distance, angle, penumbra );
-			light.name = 'SpotLight ' + ( ++ lightCount );
-			light.target.name = 'SpotLight ' + ( lightCount ) + ' Target';
-
-			light.position.set( 5, 10, 7.5 );
-
-			editor.execute( new AddObjectCommand( light ) );
-
-		} );
-		options.add( option );
-
-		// DirectionalLight
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'DirectionalLight' );
-		option.onClick( function () {
-
-			var color = 0xffffff;
-			var intensity = 1;
-
-			var light = new THREE.DirectionalLight( color, intensity );
-			light.name = 'DirectionalLight ' + ( ++ lightCount );
-			light.target.name = 'DirectionalLight ' + ( lightCount ) + ' Target';
-
-			light.position.set( 5, 10, 7.5 );
-
-			editor.execute( new AddObjectCommand( light ) );
-
-		} );
-		options.add( option );
-
-		// HemisphereLight
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'HemisphereLight' );
-		option.onClick( function () {
-
-			var skyColor = 0x00aaff;
-			var groundColor = 0xffaa00;
-			var intensity = 1;
-
-			var light = new THREE.HemisphereLight( skyColor, groundColor, intensity );
-			light.name = 'HemisphereLight ' + ( ++ lightCount );
-
-			light.position.set( 0, 10, 0 );
-
-			editor.execute( new AddObjectCommand( light ) );
-
-		} );
-		options.add( option );
-
-		// AmbientLight
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'AmbientLight' );
-		option.onClick( function() {
-
-			var color = 0x222222;
-
-			var light = new THREE.AmbientLight( color );
-			light.name = 'AmbientLight ' + ( ++ lightCount );
-
-			editor.execute( new AddObjectCommand( light ) );
-
-		} );
-		options.add( option );
-
-		//
-
-		options.add( new UI.HorizontalRule() );
-
-		// PerspectiveCamera
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'PerspectiveCamera' );
-		option.onClick( function() {
-
-			var camera = new THREE.PerspectiveCamera( 50, 1, 1, 10000 );
-			camera.name = 'PerspectiveCamera ' + ( ++ cameraCount );
-
-			editor.execute( new AddObjectCommand( camera ) );
-
-		} );
-		options.add( option );
-
-		return container;
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Menubar.Add', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Menubar.Edit = function ( editor ) {
-
-		var container = new UI.Panel();
-		container.setClass( 'menu' );
-
-		var title = new UI.Panel();
-		title.setClass( 'title' );
-		title.setTextContent( 'Edit' );
-		container.add( title );
-
-		var options = new UI.Panel();
-		options.setClass( 'options' );
-		container.add( options );
-
-		// Undo
-
-		var undo = new UI.Row();
-		undo.setClass( 'option' );
-		undo.setTextContent( 'Undo (Ctrl+Z)' );
-		undo.onClick( function () {
-
-			editor.undo();
-
-		} );
-		options.add( undo );
-
-		// Redo
-
-		var redo = new UI.Row();
-		redo.setClass( 'option' );
-		redo.setTextContent( 'Redo (Ctrl+Shift+Z)' );
-		redo.onClick( function () {
-
-			editor.redo();
-
-		} );
-		options.add( redo );
-
-		// Clear History
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'Clear History' );
-		option.onClick( function () {
-
-			if ( confirm( 'The Undo/Redo History will be cleared. Are you sure?' ) ) {
-
-				editor.history.clear();
-
-			}
-
-		} );
-		options.add( option );
-
-
-		editor.signals.historyChanged.add( function () {
-
-			var history = editor.history;
-
-			undo.setClass( 'option' );
-			redo.setClass( 'option' );
-
-			if ( history.undos.length == 0 ) {
-
-				undo.setClass( 'inactive' );
-
-			}
-
-			if ( history.redos.length == 0 ) {
-
-				redo.setClass( 'inactive' );
-
-			}
-
-		} );
-
-		// ---
-
-		options.add( new UI.HorizontalRule() );
-
-		// Clone
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'Clone' );
-		option.onClick( function () {
-
-			var object = editor.selected;
-
-			if ( object.parent === null ) return; // avoid cloning the camera or scene
-
-			object = object.clone();
-
-			editor.execute( new AddObjectCommand( object ) );
-
-		} );
-		options.add( option );
-
-		// Delete
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'Delete (Del)' );
-		option.onClick( function () {
-
-			var object = editor.selected;
-
-			if ( confirm( 'Delete ' + object.name + '?' ) === false ) return;
-
-			var parent = object.parent;
-			if ( parent === undefined ) return; // avoid deleting the camera or scene
-
-			editor.execute( new RemoveObjectCommand( object ) );
-
-		} );
-		options.add( option );
-
-		// Minify shaders
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'Minify Shaders' );
-		option.onClick( function() {
-
-			var root = editor.selected || editor.scene;
-
-			var errors = [];
-			var nMaterialsChanged = 0;
-
-			var path = [];
-
-			function getPath ( object ) {
-
-				path.length = 0;
-
-				var parent = object.parent;
-				if ( parent !== undefined ) getPath( parent );
-
-				path.push( object.name || object.uuid );
-
-				return path;
-
-			}
-
-			var cmds = [];
-			root.traverse( function ( object ) {
-
-				var material = object.material;
-
-				if ( material instanceof THREE.ShaderMaterial ) {
-
-					try {
-
-						var shader = glslprep.minifyGlsl( [
-								material.vertexShader, material.fragmentShader ] );
-
-						cmds.push( new SetMaterialValueCommand( object, 'vertexShader', shader[ 0 ] ) );
-						cmds.push( new SetMaterialValueCommand( object, 'fragmentShader', shader[ 1 ] ) );
-
-						++nMaterialsChanged;
-
-					} catch ( e ) {
-
-						var path = getPath( object ).join( "/" );
-
-						if ( e instanceof glslprep.SyntaxError )
-
-							errors.push( path + ":" +
-									e.line + ":" + e.column + ": " + e.message );
-
-						else {
-
-							errors.push( path +
-									": Unexpected error (see console for details)." );
-
-							console.error( e.stack || e );
-
-						}
-
-					}
-
-				}
-
-			} );
-
-			if ( nMaterialsChanged > 0 ) {
-
-				editor.execute( new MultiCmdsCommand( cmds ), 'Minify Shaders' );
-
-			}
-
-			window.alert( nMaterialsChanged +
-					" material(s) were changed.\n" + errors.join( "\n" ) );
-
-		} );
-		options.add( option );
-
-
-		return container;
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Menubar.Edit', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Menubar.Examples = function ( editor ) {
-
-		var container = new UI.Panel();
-		container.setClass( 'menu' );
-
-		var title = new UI.Panel();
-		title.setClass( 'title' );
-		title.setTextContent( 'Examples' );
-		container.add( title );
-
-		var options = new UI.Panel();
-		options.setClass( 'options' );
-		container.add( options );
-
-		// Examples
-
-		var items = [
-			{ title: 'Arkanoid', file: 'arkanoid.app.json' },
-			{ title: 'Camera', file: 'camera.app.json' },
-			{ title: 'Particles', file: 'particles.app.json' },
-			{ title: 'Pong', file: 'pong.app.json' },
-			{ title: 'Shaders', file: 'shaders.app.json' }
-		];
-
-		var loader = new THREE.FileLoader();
-
-		for ( var i = 0; i < items.length; i ++ ) {
-
-			( function ( i ) {
-
-				var item = items[ i ];
-
-				var option = new UI.Row();
-				option.setClass( 'option' );
-				option.setTextContent( item.title );
-				option.onClick( function () {
-
-					if ( confirm( 'Any unsaved data will be lost. Are you sure?' ) ) {
-
-						loader.load( 'examples/' + item.file, function ( text ) {
-
-							editor.clear();
-							editor.fromJSON( JSON.parse( text ) );
-
-						} );
-
-					}
-
-				} );
-				options.add( option );
-
-			} )( i );
-
-		}
-
-		return container;
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Menubar.Examples', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Menubar.File = function ( editor ) {
-
-		var NUMBER_PRECISION = 6;
-
-		function parseNumber( key, value ) {
-
-			return typeof value === 'number' ? parseFloat( value.toFixed( NUMBER_PRECISION ) ) : value;
-
-		}
-
-		//
-
-		var container = new UI.Panel();
-		container.setClass( 'menu' );
-
-		var title = new UI.Panel();
-		title.setClass( 'title' );
-		title.setTextContent( 'File' );
-		container.add( title );
-
-		var options = new UI.Panel();
-		options.setClass( 'options' );
-		container.add( options );
-
-		// New
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'New' );
-		option.onClick( function () {
-
-			if ( confirm( 'Any unsaved data will be lost. Are you sure?' ) ) {
-
-				editor.clear();
-
-			}
-
-		} );
-		options.add( option );
-
-		//
-
-		options.add( new UI.HorizontalRule() );
-
-		// Import
-
-		var form = document.createElement( 'form' );
-		form.style.display = 'none';
-		document.body.appendChild( form );
-
-		var fileInput = document.createElement( 'input' );
-		fileInput.type = 'file';
-		fileInput.addEventListener( 'change', function ( event ) {
-
-			editor.loader.loadFile( fileInput.files[ 0 ] );
-			form.reset();
-
-		} );
-		form.appendChild( fileInput );
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'Import' );
-		option.onClick( function () {
-
-			fileInput.click();
-
-		} );
-		options.add( option );
-
-		//
-
-		options.add( new UI.HorizontalRule() );
-
-		// Export Geometry
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'Export Geometry' );
-		option.onClick( function () {
-
-			var object = editor.selected;
-
-			if ( object === null ) {
-
-				alert( 'No object selected.' );
-				return;
-
-			}
-
-			var geometry = object.geometry;
-
-			if ( geometry === undefined ) {
-
-				alert( 'The selected object doesn\'t have geometry.' );
-				return;
-
-			}
-
-			var output = geometry.toJSON();
-
-			try {
-
-				output = JSON.stringify( output, parseNumber, '\t' );
-				output = output.replace( /[\n\t]+([\d\.e\-\[\]]+)/g, '$1' );
-
-			} catch ( e ) {
-
-				output = JSON.stringify( output );
-
-			}
-
-			saveString( output, 'geometry.json' );
-
-		} );
-		options.add( option );
-
-		// Export Object
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'Export Object' );
-		option.onClick( function () {
-
-			var object = editor.selected;
-
-			if ( object === null ) {
-
-				alert( 'No object selected' );
-				return;
-
-			}
-
-			var output = object.toJSON();
-
-			try {
-
-				output = JSON.stringify( output, parseNumber, '\t' );
-				output = output.replace( /[\n\t]+([\d\.e\-\[\]]+)/g, '$1' );
-
-			} catch ( e ) {
-
-				output = JSON.stringify( output );
-
-			}
-
-			saveString( output, 'model.json' );
-
-		} );
-		options.add( option );
-
-		// Export Scene
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'Export Scene' );
-		option.onClick( function () {
-
-			var output = editor.scene.toJSON();
-
-			try {
-
-				output = JSON.stringify( output, parseNumber, '\t' );
-				output = output.replace( /[\n\t]+([\d\.e\-\[\]]+)/g, '$1' );
-
-			} catch ( e ) {
-
-				output = JSON.stringify( output );
-
-			}
-
-			saveString( output, 'scene.json' );
-
-		} );
-		options.add( option );
-
-		//
-
-		options.add( new UI.HorizontalRule() );
-
-		// Export GLTF
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'Export GLTF' );
-		option.onClick( function () {
-
-			var exporter = new THREE.GLTFExporter();
-
-			exporter.parse( editor.scene, function ( result ) {
-
-				saveString( JSON.stringify( result, null, 2 ), 'scene.gltf' );
-
-			} );
-
-
-		} );
-		options.add( option );
-
-		// Export OBJ
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'Export OBJ' );
-		option.onClick( function () {
-
-			var object = editor.selected;
-
-			if ( object === null ) {
-
-				alert( 'No object selected.' );
-				return;
-
-			}
-
-			var exporter = new THREE.OBJExporter();
-
-			saveString( exporter.parse( object ), 'model.obj' );
-
-		} );
-		options.add( option );
-
-		// Export STL
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'Export STL' );
-		option.onClick( function () {
-
-			var exporter = new THREE.STLExporter();
-
-			saveString( exporter.parse( editor.scene ), 'model.stl' );
-
-		} );
-		options.add( option );
-
-		//
-
-		options.add( new UI.HorizontalRule() );
-
-		// Publish
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'Publish' );
-		option.onClick( function () {
-
-			var zip = new JSZip();
-
-			//
-
-			var output = editor.toJSON();
-			output.metadata.type = 'App';
-			delete output.history;
-
-			var vr = output.project.vr;
-
-			output = JSON.stringify( output, parseNumber, '\t' );
-			output = output.replace( /[\n\t]+([\d\.e\-\[\]]+)/g, '$1' );
-
-			zip.file( 'app.json', output );
-
-			//
-
-			var manager = new THREE.LoadingManager( function () {
-
-				save( zip.generate( { type: 'blob' } ), 'download.zip' );
-
-			} );
-
-			var loader = new THREE.FileLoader( manager );
-			loader.load( 'js/libs/app/index.html', function ( content ) {
-
-				var includes = [];
-
-				if ( vr ) {
-
-					includes.push( '<script src="js/WebVR.js"></script>' );
-
-				}
-
-				content = content.replace( '<!-- includes -->', includes.join( '\n\t\t' ) );
-
-				var editButton = '';
-
-				if ( editor.config.getKey( 'project/editable' ) ) {
-
-					editButton = `
-			var button = document.createElement( 'a' );
-			button.href = 'https://threejs.org/editor/#file=' + location.href.split( '/' ).slice( 0, - 1 ).join( '/' ) + '/app.json';
-			button.style.cssText = 'position: absolute; bottom: 20px; right: 20px; padding: 7px 10px 6px 10px; color: #fff; border: 1px solid #fff; text-decoration: none;';
-			button.target = '_blank';
-			button.textContent = 'EDIT';
-			document.body.appendChild( button );
-				`;
-
-				}
-
-				content = content.replace( '/* edit button */', editButton );
-
-				zip.file( 'index.html', content );
-
-			} );
-			loader.load( 'js/libs/app.js', function ( content ) {
-
-				zip.file( 'js/app.js', content );
-
-			} );
-			loader.load( '../build/three.min.js', function ( content ) {
-
-				zip.file( 'js/three.min.js', content );
-
-			} );
-
-			if ( vr ) {
-
-				loader.load( '../examples/js/vr/WebVR.js', function ( content ) {
-
-					zip.file( 'js/WebVR.js', content );
-
-				} );
-
-			}
-
-		} );
-		options.add( option );
-
-		/*
-		// Publish (Dropbox)
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'Publish (Dropbox)' );
-		option.onClick( function () {
-
-			var parameters = {
-				files: [
-					{ 'url': 'data:text/plain;base64,' + window.btoa( "Hello, World" ), 'filename': 'app/test.txt' }
-				]
-			};
-
-			Dropbox.save( parameters );
-
-		} );
-		options.add( option );
-		*/
-
-
-		//
-
-		var link = document.createElement( 'a' );
-		link.style.display = 'none';
-		document.body.appendChild( link ); // Firefox workaround, see #6594
-
-		function save( blob, filename ) {
-
-			link.href = URL.createObjectURL( blob );
-			link.download = filename || 'data.json';
-			link.click();
-
-			// URL.revokeObjectURL( url ); breaks Firefox...
-
-		}
-
-		function saveString( text, filename ) {
-
-			save( new Blob( [ text ], { type: 'text/plain' } ), filename );
-
-		}
-
-		return container;
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Menubar.File', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Menubar.Help = function ( editor ) {
-
-		var container = new UI.Panel();
-		container.setClass( 'menu' );
-
-		var title = new UI.Panel();
-		title.setClass( 'title' );
-		title.setTextContent( 'Help' );
-		container.add( title );
-
-		var options = new UI.Panel();
-		options.setClass( 'options' );
-		container.add( options );
-
-		// Source code
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'Source code' );
-		option.onClick( function () {
-
-			window.open( 'https://github.com/mrdoob/three.js/tree/master/editor', '_blank' );
-
-		} );
-		options.add( option );
-
-		// About
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'About' );
-		option.onClick( function () {
-
-			window.open( 'http://threejs.org', '_blank' );
-
-		} );
-		options.add( option );
-
-		return container;
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Menubar.Help', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Menubar.Play = function ( editor ) {
-
-		var signals = editor.signals;
-
-		var container = new UI.Panel();
-		container.setClass( 'menu' );
-
-		var isPlaying = false;
-
-		var title = new UI.Panel();
-		title.setClass( 'title' );
-		title.setTextContent( 'Play' );
-		title.onClick( function () {
-
-			if ( isPlaying === false ) {
-
-				isPlaying = true;
-				title.setTextContent( 'Stop' );
-				signals.startPlayer.dispatch();
-
-			} else {
-
-				isPlaying = false;
-				title.setTextContent( 'Play' );
-				signals.stopPlayer.dispatch();
-
-			}
-
-		} );
-		container.add( title );
-
-		return container;
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Menubar.Play', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Menubar.Status = function ( editor ) {
-
-		var container = new UI.Panel();
-		container.setClass( 'menu right' );
-
-		var autosave = new UI.THREE.Boolean( editor.config.getKey( 'autosave' ), 'autosave' );
-		autosave.text.setColor( '#888' );
-		autosave.onChange( function () {
-
-			var value = this.getValue();
-
-			editor.config.setKey( 'autosave', value );
-
-			if ( value === true ) {
-
-				editor.signals.sceneGraphChanged.dispatch();
-
-			}
-
-		} );
-		container.add( autosave );
-
-		editor.signals.savingStarted.add( function () {
-
-			autosave.text.setTextDecoration( 'underline' );
-
-		} );
-
-		editor.signals.savingFinished.add( function () {
-
-			autosave.text.setTextDecoration( 'none' );
-
-		} );
-
-		var version = new UI.Text( 'r' + THREE.REVISION );
-		version.setClass( 'title' );
-		version.setOpacity( 0.5 );
-		container.add( version );
-
-		return container;
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Menubar.Status', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Menubar.View = function ( editor ) {
-
-		var container = new UI.Panel();
-		container.setClass( 'menu' );
-
-		var title = new UI.Panel();
-		title.setClass( 'title' );
-		title.setTextContent( 'View' );
-		container.add( title );
-
-		var options = new UI.Panel();
-		options.setClass( 'options' );
-		container.add( options );
-
-		// VR mode
-
-		var option = new UI.Row();
-		option.setClass( 'option' );
-		option.setTextContent( 'VR mode' );
-		option.onClick( function () {
-
-			if ( WEBVR.isAvailable() === true ) {
-
-				editor.signals.enterVR.dispatch();
-
-			} else {
-
-				alert( 'WebVR not available' );
-
-			}
-
-		} );
-		options.add( option );
-
-		return container;
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Menubar.View', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Sidebar', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Sidebar.Animation = function ( editor ) {
-
-		var signals = editor.signals;
-
-		var options = {};
-		var possibleAnimations = {};
-
-		var container = new UI.Panel();
-		container.setDisplay( 'none' );
-
-		container.add( new UI.Text( 'Animation' ).setTextTransform( 'uppercase' ) );
-		container.add( new UI.Break() );
-		container.add( new UI.Break() );
-
-		var animationsRow = new UI.Row();
-		container.add( animationsRow );
-
-		return container;
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Sidebar.Animation', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Sidebar.Geometry = function ( editor ) {
-
-		var signals = editor.signals;
-
-		var container = new UI.Panel();
-		container.setBorderTop( '0' );
-		container.setPaddingTop( '20px' );
-
-		// Actions
-
-		/*
-		var objectActions = new UI.Select().setPosition( 'absolute' ).setRight( '8px' ).setFontSize( '11px' );
-		objectActions.setOptions( {
-
-			'Actions': 'Actions',
-			'Center': 'Center',
-			'Convert': 'Convert',
-			'Flatten': 'Flatten'
-
-		} );
-		objectActions.onClick( function ( event ) {
-
-			event.stopPropagation(); // Avoid panel collapsing
-
-		} );
-		objectActions.onChange( function ( event ) {
-
-			var action = this.getValue();
-
-			var object = editor.selected;
-			var geometry = object.geometry;
-
-			if ( confirm( action + ' ' + object.name + '?' ) === false ) return;
-
-			switch ( action ) {
-
-				case 'Center':
-
-					var offset = geometry.center();
-
-					var newPosition = object.position.clone();
-					newPosition.sub( offset );
-					editor.execute( new SetPositionCommand( object, newPosition ) );
-
-					editor.signals.geometryChanged.dispatch( object );
-
-					break;
-
-				case 'Convert':
-
-					if ( geometry instanceof THREE.Geometry ) {
-
-						editor.execute( new SetGeometryCommand( object, new THREE.BufferGeometry().fromGeometry( geometry ) ) );
-
-					}
-
-					break;
-
-				case 'Flatten':
-
-					var newGeometry = geometry.clone();
-					newGeometry.uuid = geometry.uuid;
-					newGeometry.applyMatrix( object.matrix );
-
-					var cmds = [ new SetGeometryCommand( object, newGeometry ),
-						new SetPositionCommand( object, new THREE.Vector3( 0, 0, 0 ) ),
-						new SetRotationCommand( object, new THREE.Euler( 0, 0, 0 ) ),
-						new SetScaleCommand( object, new THREE.Vector3( 1, 1, 1 ) ) ];
-
-					editor.execute( new MultiCmdsCommand( cmds ), 'Flatten Geometry' );
-
-					break;
-
-			}
-
-			this.setValue( 'Actions' );
-
-		} );
-		container.addStatic( objectActions );
-		*/
-
-		// type
-
-		var geometryTypeRow = new UI.Row();
-		var geometryType = new UI.Text();
-
-		geometryTypeRow.add( new UI.Text( 'Type' ).setWidth( '90px' ) );
-		geometryTypeRow.add( geometryType );
-
-		container.add( geometryTypeRow );
-
-		// uuid
-
-		var geometryUUIDRow = new UI.Row();
-		var geometryUUID = new UI.Input().setWidth( '102px' ).setFontSize( '12px' ).setDisabled( true );
-		var geometryUUIDRenew = new UI.Button( 'New' ).setMarginLeft( '7px' ).onClick( function () {
-
-			geometryUUID.setValue( THREE.Math.generateUUID() );
-
-			editor.execute( new SetGeometryValueCommand( editor.selected, 'uuid', geometryUUID.getValue() ) );
-
-		} );
-
-		geometryUUIDRow.add( new UI.Text( 'UUID' ).setWidth( '90px' ) );
-		geometryUUIDRow.add( geometryUUID );
-		geometryUUIDRow.add( geometryUUIDRenew );
-
-		container.add( geometryUUIDRow );
-
-		// name
-
-		var geometryNameRow = new UI.Row();
-		var geometryName = new UI.Input().setWidth( '150px' ).setFontSize( '12px' ).onChange( function () {
-
-			editor.execute( new SetGeometryValueCommand( editor.selected, 'name', geometryName.getValue() ) );
-
-		} );
-
-		geometryNameRow.add( new UI.Text( 'Name' ).setWidth( '90px' ) );
-		geometryNameRow.add( geometryName );
-
-		container.add( geometryNameRow );
-
-		// geometry
-
-		container.add( new Sidebar.Geometry.Geometry( editor ) );
-
-		// buffergeometry
-
-		container.add( new Sidebar.Geometry.BufferGeometry( editor ) );
-
-		// parameters
-
-		var parameters = new UI.Span();
-		container.add( parameters );
-
-
-		//
-
-		function build() {
-
-			var object = editor.selected;
-
-			if ( object && object.geometry ) {
-
-				var geometry = object.geometry;
-
-				container.setDisplay( 'block' );
-
-				geometryType.setValue( geometry.type );
-
-				geometryUUID.setValue( geometry.uuid );
-				geometryName.setValue( geometry.name );
-
-				//
-
-				parameters.clear();
-
-				if ( geometry.type === 'BufferGeometry' || geometry.type === 'Geometry' ) {
-
-					parameters.add( new Sidebar.Geometry.Modifiers( editor, object ) );
-
-				} else if ( Sidebar.Geometry[ geometry.type ] !== undefined ) {
-
-					parameters.add( new Sidebar.Geometry[ geometry.type ]( editor, object ) );
-
-				}
-
-			} else {
-
-				container.setDisplay( 'none' );
-
-			}
-
-		}
-
-		signals.objectSelected.add( build );
-		signals.geometryChanged.add( build );
-
-		return container;
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Sidebar.Geometry', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Sidebar.Geometry.BoxGeometry = function ( editor, object ) {
-
-		var signals = editor.signals;
-
-		var container = new UI.Row();
-
-		var geometry = object.geometry;
-		var parameters = geometry.parameters;
-
-		// width
-
-		var widthRow = new UI.Row();
-		var width = new UI.Number( parameters.width ).onChange( update );
-
-		widthRow.add( new UI.Text( 'Width' ).setWidth( '90px' ) );
-		widthRow.add( width );
-
-		container.add( widthRow );
-
-		// height
-
-		var heightRow = new UI.Row();
-		var height = new UI.Number( parameters.height ).onChange( update );
-
-		heightRow.add( new UI.Text( 'Height' ).setWidth( '90px' ) );
-		heightRow.add( height );
-
-		container.add( heightRow );
-
-		// depth
-
-		var depthRow = new UI.Row();
-		var depth = new UI.Number( parameters.depth ).onChange( update );
-
-		depthRow.add( new UI.Text( 'Depth' ).setWidth( '90px' ) );
-		depthRow.add( depth );
-
-		container.add( depthRow );
-
-		// widthSegments
-
-		var widthSegmentsRow = new UI.Row();
-		var widthSegments = new UI.Integer( parameters.widthSegments ).setRange( 1, Infinity ).onChange( update );
-
-		widthSegmentsRow.add( new UI.Text( 'Width segments' ).setWidth( '90px' ) );
-		widthSegmentsRow.add( widthSegments );
-
-		container.add( widthSegmentsRow );
-
-		// heightSegments
-
-		var heightSegmentsRow = new UI.Row();
-		var heightSegments = new UI.Integer( parameters.heightSegments ).setRange( 1, Infinity ).onChange( update );
-
-		heightSegmentsRow.add( new UI.Text( 'Height segments' ).setWidth( '90px' ) );
-		heightSegmentsRow.add( heightSegments );
-
-		container.add( heightSegmentsRow );
-
-		// depthSegments
-
-		var depthSegmentsRow = new UI.Row();
-		var depthSegments = new UI.Integer( parameters.depthSegments ).setRange( 1, Infinity ).onChange( update );
-
-		depthSegmentsRow.add( new UI.Text( 'Depth segments' ).setWidth( '90px' ) );
-		depthSegmentsRow.add( depthSegments );
-
-		container.add( depthSegmentsRow );
-
-		//
-
-		function update() {
-
-			editor.execute( new SetGeometryCommand( object, new THREE[ geometry.type ](
-				width.getValue(),
-				height.getValue(),
-				depth.getValue(),
-				widthSegments.getValue(),
-				heightSegments.getValue(),
-				depthSegments.getValue()
-			) ) );
-
-		}
-
-		return container;
-
-	};
-
-	Sidebar.Geometry.BoxBufferGeometry = Sidebar.Geometry.BoxGeometry;
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Sidebar.Geometry.BoxGeometry', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Sidebar.Geometry.BufferGeometry = function ( editor ) {
-
-		var signals = editor.signals;
-
-		var container = new UI.Row();
-
-		function update( object ) {
-
-			if ( object === null ) return; // objectSelected.dispatch( null )
-			if ( object === undefined ) return;
-
-			var geometry = object.geometry;
-
-			if ( geometry instanceof THREE.BufferGeometry ) {
-
-				container.clear();
-				container.setDisplay( 'block' );
-
-				var index = geometry.index;
-
-				if ( index !== null ) {
-
-					var panel = new UI.Row();
-					panel.add( new UI.Text( 'index' ).setWidth( '90px' ) );
-					panel.add( new UI.Text( ( index.count ).format() ).setFontSize( '12px' ) );
-					container.add( panel );
-
-				}
-
-				var attributes = geometry.attributes;
-
-				for ( var name in attributes ) {
-
-					var attribute = attributes[ name ];
-
-					var panel = new UI.Row();
-					panel.add( new UI.Text( name ).setWidth( '90px' ) );
-					panel.add( new UI.Text( ( attribute.count ).format() + ' (' + attribute.itemSize + ')' ).setFontSize( '12px' ) );
-					container.add( panel );
-
-				}
-
-			} else {
-
-				container.setDisplay( 'none' );
-
-			}
-
-		}
-
-		signals.objectSelected.add( update );
-		signals.geometryChanged.add( update );
-
-		return container;
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Sidebar.Geometry.BufferGeometry', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Sidebar.Geometry.CircleGeometry = function ( editor, object ) {
-
-		var signals = editor.signals;
-
-		var container = new UI.Row();
-
-		var geometry = object.geometry;
-		var parameters = geometry.parameters;
-
-		// radius
-
-		var radiusRow = new UI.Row();
-		var radius = new UI.Number( parameters.radius ).onChange( update );
-
-		radiusRow.add( new UI.Text( 'Radius' ).setWidth( '90px' ) );
-		radiusRow.add( radius );
-
-		container.add( radiusRow );
-
-		// segments
-
-		var segmentsRow = new UI.Row();
-		var segments = new UI.Integer( parameters.segments ).setRange( 3, Infinity ).onChange( update );
-
-		segmentsRow.add( new UI.Text( 'Segments' ).setWidth( '90px' ) );
-		segmentsRow.add( segments );
-
-		container.add( segmentsRow );
-
-		// thetaStart
-
-		var thetaStartRow = new UI.Row();
-		var thetaStart = new UI.Number( parameters.thetaStart ).onChange( update );
-
-		thetaStartRow.add( new UI.Text( 'Theta start' ).setWidth( '90px' ) );
-		thetaStartRow.add( thetaStart );
-
-		container.add( thetaStartRow );
-
-		// thetaLength
-
-		var thetaLengthRow = new UI.Row();
-		var thetaLength = new UI.Number( parameters.thetaLength ).onChange( update );
-
-		thetaLengthRow.add( new UI.Text( 'Theta length' ).setWidth( '90px' ) );
-		thetaLengthRow.add( thetaLength );
-
-		container.add( thetaLengthRow );
-
-		//
-
-		function update() {
-
-			editor.execute( new SetGeometryCommand( object, new THREE[ geometry.type ](
-				radius.getValue(),
-				segments.getValue(),
-				thetaStart.getValue(),
-				thetaLength.getValue()
-			) ) );
-
-		}
-
-		return container;
-
-	};
-
-	Sidebar.Geometry.CircleBufferGeometry = Sidebar.Geometry.CircleGeometry;
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Sidebar.Geometry.CircleGeometry', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Sidebar.Geometry.CylinderGeometry = function ( editor, object ) {
-
-		var signals = editor.signals;
-
-		var container = new UI.Row();
-
-		var geometry = object.geometry;
-		var parameters = geometry.parameters;
-
-		// radiusTop
-
-		var radiusTopRow = new UI.Row();
-		var radiusTop = new UI.Number( parameters.radiusTop ).onChange( update );
-
-		radiusTopRow.add( new UI.Text( 'Radius top' ).setWidth( '90px' ) );
-		radiusTopRow.add( radiusTop );
-
-		container.add( radiusTopRow );
-
-		// radiusBottom
-
-		var radiusBottomRow = new UI.Row();
-		var radiusBottom = new UI.Number( parameters.radiusBottom ).onChange( update );
-
-		radiusBottomRow.add( new UI.Text( 'Radius bottom' ).setWidth( '90px' ) );
-		radiusBottomRow.add( radiusBottom );
-
-		container.add( radiusBottomRow );
-
-		// height
-
-		var heightRow = new UI.Row();
-		var height = new UI.Number( parameters.height ).onChange( update );
-
-		heightRow.add( new UI.Text( 'Height' ).setWidth( '90px' ) );
-		heightRow.add( height );
-
-		container.add( heightRow );
-
-		// radialSegments
-
-		var radialSegmentsRow = new UI.Row();
-		var radialSegments = new UI.Integer( parameters.radialSegments ).setRange( 1, Infinity ).onChange( update );
-
-		radialSegmentsRow.add( new UI.Text( 'Radial segments' ).setWidth( '90px' ) );
-		radialSegmentsRow.add( radialSegments );
-
-		container.add( radialSegmentsRow );
-
-		// heightSegments
-
-		var heightSegmentsRow = new UI.Row();
-		var heightSegments = new UI.Integer( parameters.heightSegments ).setRange( 1, Infinity ).onChange( update );
-
-		heightSegmentsRow.add( new UI.Text( 'Height segments' ).setWidth( '90px' ) );
-		heightSegmentsRow.add( heightSegments );
-
-		container.add( heightSegmentsRow );
-
-		// openEnded
-
-		var openEndedRow = new UI.Row();
-		var openEnded = new UI.Checkbox( parameters.openEnded ).onChange( update );
-
-		openEndedRow.add( new UI.Text( 'Open ended' ).setWidth( '90px' ) );
-		openEndedRow.add( openEnded );
-
-		container.add( openEndedRow );
-
-		//
-
-		function update() {
-
-			editor.execute( new SetGeometryCommand( object, new THREE[ geometry.type ](
-				radiusTop.getValue(),
-				radiusBottom.getValue(),
-				height.getValue(),
-				radialSegments.getValue(),
-				heightSegments.getValue(),
-				openEnded.getValue()
-			) ) );
-
-		}
-
-		return container;
-
-	};
-
-	Sidebar.Geometry.CylinderBufferGeometry = Sidebar.Geometry.CylinderGeometry;
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Sidebar.Geometry.CylinderGeometry', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Sidebar.Geometry', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Sidebar.Geometry.IcosahedronGeometry = function ( editor, object ) {
-
-		var signals = editor.signals;
-
-		var container = new UI.Row();
-
-		var geometry = object.geometry;
-		var parameters = geometry.parameters;
-
-		// radius
-
-		var radiusRow = new UI.Row();
-		var radius = new UI.Number( parameters.radius ).onChange( update );
-
-		radiusRow.add( new UI.Text( 'Radius' ).setWidth( '90px' ) );
-		radiusRow.add( radius );
-
-		container.add( radiusRow );
-
-		// detail
-
-		var detailRow = new UI.Row();
-		var detail = new UI.Integer( parameters.detail ).setRange( 0, Infinity ).onChange( update );
-
-		detailRow.add( new UI.Text( 'Detail' ).setWidth( '90px' ) );
-		detailRow.add( detail );
-
-		container.add( detailRow );
-
-
-		//
-
-		function update() {
-
-			editor.execute( new SetGeometryCommand( object, new THREE[ geometry.type ](
-				radius.getValue(),
-				detail.getValue()
-			) ) );
-
-			signals.objectChanged.dispatch( object );
-
-		}
-
-		return container;
-
-	};
-
-	Sidebar.Geometry.IcosahedronBufferGeometry = Sidebar.Geometry.IcosahedronGeometry;
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Sidebar.Geometry.IcosahedronGeometry', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author rfm1201
-	 */
-
-	Sidebar.Geometry.LatheGeometry = function( editor, object ) {
-
-		var signals = editor.signals;
-
-		var container = new UI.Row();
-
-		var geometry = object.geometry;
-		var parameters = geometry.parameters;
-
-		// segments
-
-		var segmentsRow = new UI.Row();
-		var segments = new UI.Integer( parameters.segments ).onChange( update );
-
-		segmentsRow.add( new UI.Text( 'Segments' ).setWidth( '90px' ) );
-		segmentsRow.add( segments );
-
-		container.add( segmentsRow );
-
-		// phiStart
-
-		var phiStartRow = new UI.Row();
-		var phiStart = new UI.Number( parameters.phiStart * 180 / Math.PI ).onChange( update );
-
-		phiStartRow.add( new UI.Text( 'Phi start (°)' ).setWidth( '90px' ) );
-		phiStartRow.add( phiStart );
-
-		container.add( phiStartRow );
-
-		// phiLength
-
-		var phiLengthRow = new UI.Row();
-		var phiLength = new UI.Number( parameters.phiLength * 180 / Math.PI ).onChange( update );
-
-		phiLengthRow.add( new UI.Text( 'Phi length (°)' ).setWidth( '90px' ) );
-		phiLengthRow.add( phiLength );
-
-		container.add( phiLengthRow );
-
-		// points
-
-		var lastPointIdx = 0;
-		var pointsUI = [];
-
-		var pointsRow = new UI.Row();
-		pointsRow.add( new UI.Text( 'Points' ).setWidth( '90px' ) );
-
-		var points = new UI.Span().setDisplay( 'inline-block' );
-		pointsRow.add( points );
-
-		var pointsList = new UI.Div();
-		points.add( pointsList );
-
-		for ( var i = 0; i < parameters.points.length; i ++ ) {
-
-			var point = parameters.points[ i ];
-			pointsList.add( createPointRow( point.x, point.y ) );
-
-		}
-
-		var addPointButton = new UI.Button( '+' ).onClick( function() {
-
-			if( pointsUI.length === 0 ){
-
-				pointsList.add( createPointRow( 0, 0 ) );
-
-			} else {
-
-				var point = pointsUI[ pointsUI.length - 1 ];
-
-				pointsList.add( createPointRow( point.x.getValue(), point.y.getValue() ) );
-
-			}
-
-			update();
-
-		} );
-		points.add( addPointButton );
-
-		container.add( pointsRow );
-
-		//
-
-		function createPointRow( x, y ) {
-
-			var pointRow = new UI.Div();
-			var lbl = new UI.Text( lastPointIdx + 1 ).setWidth( '20px' );
-			var txtX = new UI.Number( x ).setRange( 0, Infinity ).setWidth( '40px' ).onChange( update );
-			var txtY = new UI.Number( y ).setWidth( '40px' ).onChange( update );
-			var idx = lastPointIdx;
-			var btn = new UI.Button( '-' ).onClick( function() {
-
-				deletePointRow( idx );
-
-			} );
-
-			pointsUI.push( { row: pointRow, lbl: lbl, x: txtX, y: txtY } );
-			lastPointIdx ++;
-			pointRow.add( lbl, txtX, txtY, btn );
-
-			return pointRow;
-
-		}
-
-		function deletePointRow( idx ) {
-
-			if ( ! pointsUI[ idx ] ) return;
-
-			pointsList.remove( pointsUI[ idx ].row );
-			pointsUI[ idx ] = null;
-
-			update();
-
-		}
-
-		function update() {
-
-			var points = [];
-			var count = 0;
-
-			for ( var i = 0; i < pointsUI.length; i ++ ) {
-
-				var pointUI = pointsUI[ i ];
-
-				if ( ! pointUI ) continue;
-
-				points.push( new THREE.Vector2( pointUI.x.getValue(), pointUI.y.getValue() ) );
-				count ++;
-				pointUI.lbl.setValue( count );
-
-			}
-
-			editor.execute( new SetGeometryCommand( object, new THREE[ geometry.type ](
-				points,
-				segments.getValue(),
-				phiStart.getValue() / 180 * Math.PI,
-				phiLength.getValue() / 180 * Math.PI
-			) ) );
-
-		}
-
-		return container;
-
-	};
-
-	Sidebar.Geometry.LatheBufferGeometry = Sidebar.Geometry.LatheGeometry;
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Sidebar.Geometry.LatheGeometry', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Sidebar.Geometry.Modifiers = function ( editor, object ) {
-
-		var signals = editor.signals;
-
-		var container = new UI.Row().setPaddingLeft( '90px' );
-
-		var geometry = object.geometry;
-
-		// Compute Vertex Normals
-
-		var button = new UI.Button( 'Compute Vertex Normals' );
-		button.onClick( function () {
-
-			geometry.computeVertexNormals();
-
-			if ( geometry instanceof THREE.BufferGeometry ) {
-
-				geometry.attributes.normal.needsUpdate = true;
-
-			} else {
-
-				geometry.normalsNeedUpdate = true;
-
-			}
-
-			signals.geometryChanged.dispatch( object );
-
-		} );
-
-		container.add( button );
-
-		//
-
-		return container;
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Sidebar.Geometry.Modifiers', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Sidebar.Geometry.PlaneGeometry = function ( editor, object ) {
-
-		var signals = editor.signals;
-
-		var container = new UI.Row();
-
-		var geometry = object.geometry;
-		var parameters = geometry.parameters;
-
-		// width
-
-		var widthRow = new UI.Row();
-		var width = new UI.Number( parameters.width ).onChange( update );
-
-		widthRow.add( new UI.Text( 'Width' ).setWidth( '90px' ) );
-		widthRow.add( width );
-
-		container.add( widthRow );
-
-		// height
-
-		var heightRow = new UI.Row();
-		var height = new UI.Number( parameters.height ).onChange( update );
-
-		heightRow.add( new UI.Text( 'Height' ).setWidth( '90px' ) );
-		heightRow.add( height );
-
-		container.add( heightRow );
-
-		// widthSegments
-
-		var widthSegmentsRow = new UI.Row();
-		var widthSegments = new UI.Integer( parameters.widthSegments ).setRange( 1, Infinity ).onChange( update );
-
-		widthSegmentsRow.add( new UI.Text( 'Width segments' ).setWidth( '90px' ) );
-		widthSegmentsRow.add( widthSegments );
-
-		container.add( widthSegmentsRow );
-
-		// heightSegments
-
-		var heightSegmentsRow = new UI.Row();
-		var heightSegments = new UI.Integer( parameters.heightSegments ).setRange( 1, Infinity ).onChange( update );
-
-		heightSegmentsRow.add( new UI.Text( 'Height segments' ).setWidth( '90px' ) );
-		heightSegmentsRow.add( heightSegments );
-
-		container.add( heightSegmentsRow );
-
-
-		//
-
-		function update() {
-
-			editor.execute( new SetGeometryCommand( object, new THREE[ geometry.type ](
-				width.getValue(),
-				height.getValue(),
-				widthSegments.getValue(),
-				heightSegments.getValue()
-			) ) );
-
-		}
-
-		return container;
-
-	};
-
-	Sidebar.Geometry.PlaneBufferGeometry = Sidebar.Geometry.PlaneGeometry;
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Sidebar.Geometry.PlaneGeometry', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Sidebar.Geometry.SphereGeometry = function ( editor, object ) {
-
-		var signals = editor.signals;
-
-		var container = new UI.Row();
-
-		var geometry = object.geometry;
-		var parameters = geometry.parameters;
-
-		// radius
-
-		var radiusRow = new UI.Row();
-		var radius = new UI.Number( parameters.radius ).onChange( update );
-
-		radiusRow.add( new UI.Text( 'Radius' ).setWidth( '90px' ) );
-		radiusRow.add( radius );
-
-		container.add( radiusRow );
-
-		// widthSegments
-
-		var widthSegmentsRow = new UI.Row();
-		var widthSegments = new UI.Integer( parameters.widthSegments ).setRange( 1, Infinity ).onChange( update );
-
-		widthSegmentsRow.add( new UI.Text( 'Width segments' ).setWidth( '90px' ) );
-		widthSegmentsRow.add( widthSegments );
-
-		container.add( widthSegmentsRow );
-
-		// heightSegments
-
-		var heightSegmentsRow = new UI.Row();
-		var heightSegments = new UI.Integer( parameters.heightSegments ).setRange( 1, Infinity ).onChange( update );
-
-		heightSegmentsRow.add( new UI.Text( 'Height segments' ).setWidth( '90px' ) );
-		heightSegmentsRow.add( heightSegments );
-
-		container.add( heightSegmentsRow );
-
-		// phiStart
-
-		var phiStartRow = new UI.Row();
-		var phiStart = new UI.Number( parameters.phiStart ).onChange( update );
-
-		phiStartRow.add( new UI.Text( 'Phi start' ).setWidth( '90px' ) );
-		phiStartRow.add( phiStart );
-
-		container.add( phiStartRow );
-
-		// phiLength
-
-		var phiLengthRow = new UI.Row();
-		var phiLength = new UI.Number( parameters.phiLength ).onChange( update );
-
-		phiLengthRow.add( new UI.Text( 'Phi length' ).setWidth( '90px' ) );
-		phiLengthRow.add( phiLength );
-
-		container.add( phiLengthRow );
-
-		// thetaStart
-
-		var thetaStartRow = new UI.Row();
-		var thetaStart = new UI.Number( parameters.thetaStart ).onChange( update );
-
-		thetaStartRow.add( new UI.Text( 'Theta start' ).setWidth( '90px' ) );
-		thetaStartRow.add( thetaStart );
-
-		container.add( thetaStartRow );
-
-		// thetaLength
-
-		var thetaLengthRow = new UI.Row();
-		var thetaLength = new UI.Number( parameters.thetaLength ).onChange( update );
-
-		thetaLengthRow.add( new UI.Text( 'Theta length' ).setWidth( '90px' ) );
-		thetaLengthRow.add( thetaLength );
-
-		container.add( thetaLengthRow );
-
-
-		//
-
-		function update() {
-
-			editor.execute( new SetGeometryCommand( object, new THREE[ geometry.type ](
-				radius.getValue(),
-				widthSegments.getValue(),
-				heightSegments.getValue(),
-				phiStart.getValue(),
-				phiLength.getValue(),
-				thetaStart.getValue(),
-				thetaLength.getValue()
-			) ) );
-
-		}
-
-		return container;
-
-	};
-
-	Sidebar.Geometry.SphereBufferGeometry = Sidebar.Geometry.SphereGeometry;
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Sidebar.Geometry.SphereGeometry', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author tschw
-	 */
-
-	Sidebar.Geometry.TeapotBufferGeometry = function ( signals, object ) {
-
-		var container = new UI.Row();
-
-		var parameters = object.geometry.parameters;
-
-		// size
-
-		var sizeRow = new UI.Row();
-		var size = new UI.Number( parameters.size ).onChange( update );
-
-		sizeRow.add( new UI.Text( 'Size' ).setWidth( '90px' ) );
-		sizeRow.add( size );
-
-		container.add( sizeRow );
-
-		// segments
-
-		var segmentsRow = new UI.Row();
-		var segments = new UI.Integer( parameters.segments ).setRange( 1, Infinity ).onChange( update );
-
-		segmentsRow.add( new UI.Text( 'Segments' ).setWidth( '90px' ) );
-		segmentsRow.add( segments );
-
-		container.add( segmentsRow );
-
-		// bottom
-
-		var bottomRow = new UI.Row();
-		var bottom = new UI.Checkbox( parameters.bottom ).onChange( update );
-
-		bottomRow.add( new UI.Text( 'Bottom' ).setWidth( '90px' ) );
-		bottomRow.add( bottom );
-
-		container.add( bottomRow );
-
-		// lid
-
-		var lidRow = new UI.Row();
-		var lid = new UI.Checkbox( parameters.lid ).onChange( update );
-
-		lidRow.add( new UI.Text( 'Lid' ).setWidth( '90px' ) );
-		lidRow.add( lid );
-
-		container.add( lidRow );
-
-		// body
-
-		var bodyRow = new UI.Row();
-		var body = new UI.Checkbox( parameters.body ).onChange( update );
-
-		bodyRow.add( new UI.Text( 'Body' ).setWidth( '90px' ) );
-		bodyRow.add( body );
-
-		container.add( bodyRow );
-
-		// fitted lid
-
-		var fitLidRow = new UI.Row();
-		var fitLid = new UI.Checkbox( parameters.fitLid ).onChange( update );
-
-		fitLidRow.add( new UI.Text( 'Fitted Lid' ).setWidth( '90px' ) );
-		fitLidRow.add( fitLid );
-
-		container.add( fitLidRow );
-
-		// blinn-sized
-
-		var blinnRow = new UI.Row();
-		var blinn = new UI.Checkbox( parameters.blinn ).onChange( update );
-
-		blinnRow.add( new UI.Text( 'Blinn-scaled' ).setWidth( '90px' ) );
-		blinnRow.add( blinn );
-
-		container.add( blinnRow );
-
-		function update() {
-
-			object.geometry.dispose();
-
-			object.geometry = new THREE.TeapotBufferGeometry(
-				size.getValue(),
-				segments.getValue(),
-				bottom.getValue(),
-				lid.getValue(),
-				body.getValue(),
-				fitLid.getValue(),
-				blinn.getValue()
-			);
-
-			object.geometry.computeBoundingSphere();
-
-			signals.geometryChanged.dispatch( object );
-
-		}
-
-		return container;
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Sidebar.Geometry.TeapotBufferGeometry', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Sidebar.Geometry.TorusGeometry = function ( editor, object ) {
-
-		var signals = editor.signals;
-
-		var container = new UI.Row();
-
-		var geometry = object.geometry;
-		var parameters = geometry.parameters;
-
-		// radius
-
-		var radiusRow = new UI.Row();
-		var radius = new UI.Number( parameters.radius ).onChange( update );
-
-		radiusRow.add( new UI.Text( 'Radius' ).setWidth( '90px' ) );
-		radiusRow.add( radius );
-
-		container.add( radiusRow );
-
-		// tube
-
-		var tubeRow = new UI.Row();
-		var tube = new UI.Number( parameters.tube ).onChange( update );
-
-		tubeRow.add( new UI.Text( 'Tube' ).setWidth( '90px' ) );
-		tubeRow.add( tube );
-
-		container.add( tubeRow );
-
-		// radialSegments
-
-		var radialSegmentsRow = new UI.Row();
-		var radialSegments = new UI.Integer( parameters.radialSegments ).setRange( 1, Infinity ).onChange( update );
-
-		radialSegmentsRow.add( new UI.Text( 'Radial segments' ).setWidth( '90px' ) );
-		radialSegmentsRow.add( radialSegments );
-
-		container.add( radialSegmentsRow );
-
-		// tubularSegments
-
-		var tubularSegmentsRow = new UI.Row();
-		var tubularSegments = new UI.Integer( parameters.tubularSegments ).setRange( 1, Infinity ).onChange( update );
-
-		tubularSegmentsRow.add( new UI.Text( 'Tubular segments' ).setWidth( '90px' ) );
-		tubularSegmentsRow.add( tubularSegments );
-
-		container.add( tubularSegmentsRow );
-
-		// arc
-
-		var arcRow = new UI.Row();
-		var arc = new UI.Number( parameters.arc ).onChange( update );
-
-		arcRow.add( new UI.Text( 'Arc' ).setWidth( '90px' ) );
-		arcRow.add( arc );
-
-		container.add( arcRow );
-
-
-		//
-
-		function update() {
-
-			editor.execute( new SetGeometryCommand( object, new THREE[ geometry.type ](
-				radius.getValue(),
-				tube.getValue(),
-				radialSegments.getValue(),
-				tubularSegments.getValue(),
-				arc.getValue()
-			) ) );
-
-		}
-
-		return container;
-
-	};
-
-	Sidebar.Geometry.TorusBufferGeometry = Sidebar.Geometry.TorusGeometry;
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Sidebar.Geometry.TorusGeometry', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Sidebar.Geometry.TorusKnotGeometry = function ( editor, object ) {
-
-		var signals = editor.signals;
-
-		var container = new UI.Row();
-
-		var geometry = object.geometry;
-		var parameters = geometry.parameters;
-
-		// radius
-
-		var radiusRow = new UI.Row();
-		var radius = new UI.Number( parameters.radius ).onChange( update );
-
-		radiusRow.add( new UI.Text( 'Radius' ).setWidth( '90px' ) );
-		radiusRow.add( radius );
-
-		container.add( radiusRow );
-
-		// tube
-
-		var tubeRow = new UI.Row();
-		var tube = new UI.Number( parameters.tube ).onChange( update );
-
-		tubeRow.add( new UI.Text( 'Tube' ).setWidth( '90px' ) );
-		tubeRow.add( tube );
-
-		container.add( tubeRow );
-
-		// tubularSegments
-
-		var tubularSegmentsRow = new UI.Row();
-		var tubularSegments = new UI.Integer( parameters.tubularSegments ).setRange( 1, Infinity ).onChange( update );
-
-		tubularSegmentsRow.add( new UI.Text( 'Tubular segments' ).setWidth( '90px' ) );
-		tubularSegmentsRow.add( tubularSegments );
-
-		container.add( tubularSegmentsRow );
-
-		// radialSegments
-
-		var radialSegmentsRow = new UI.Row();
-		var radialSegments = new UI.Integer( parameters.radialSegments ).setRange( 1, Infinity ).onChange( update );
-
-		radialSegmentsRow.add( new UI.Text( 'Radial segments' ).setWidth( '90px' ) );
-		radialSegmentsRow.add( radialSegments );
-
-		container.add( radialSegmentsRow );
-
-		// p
-
-		var pRow = new UI.Row();
-		var p = new UI.Number( parameters.p ).onChange( update );
-
-		pRow.add( new UI.Text( 'P' ).setWidth( '90px' ) );
-		pRow.add( p );
-
-		container.add( pRow );
-
-		// q
-
-		var qRow = new UI.Row();
-		var q = new UI.Number( parameters.q ).onChange( update );
-
-		pRow.add( new UI.Text( 'Q' ).setWidth( '90px' ) );
-		pRow.add( q );
-
-		container.add( qRow );
-
-
-		//
-
-		function update() {
-
-			editor.execute( new SetGeometryCommand( object, new THREE[ geometry.type ](
-				radius.getValue(),
-				tube.getValue(),
-				tubularSegments.getValue(),
-				radialSegments.getValue(),
-				p.getValue(),
-				q.getValue()
-			) ) );
-
-		}
-
-		return container;
-
-	};
-
-	Sidebar.Geometry.TorusKnotBufferGeometry = Sidebar.Geometry.TorusKnotGeometry;
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Sidebar.Geometry.TorusKnotGeometry', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author dforrer / https://github.com/dforrer
-	 * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
-	 */
-
-	Sidebar.History = function ( editor ) {
-
-		var signals = editor.signals;
-
-		var config = editor.config;
-
-		var history = editor.history;
-
-		var container = new UI.Panel();
-
-		container.add( new UI.Text( 'HISTORY' ) );
-
-		//
-
-		var persistent = new UI.THREE.Boolean( config.getKey( 'settings/history' ), 'persistent' );
-		persistent.setPosition( 'absolute' ).setRight( '8px' );
-		persistent.onChange( function () {
-
-			var value = this.getValue();
-
-			config.setKey( 'settings/history', value );
-
-			if ( value ) {
-
-				alert( 'The history will be preserved across sessions.\nThis can have an impact on performance when working with textures.' );
-
-				var lastUndoCmd = history.undos[ history.undos.length - 1 ];
-				var lastUndoId = ( lastUndoCmd !== undefined ) ? lastUndoCmd.id : 0;
-				editor.history.enableSerialization( lastUndoId );
-
-			} else {
-
-				signals.historyChanged.dispatch();
-
-			}
-
-		} );
-		container.add( persistent );
-
-		container.add( new UI.Break(), new UI.Break() );
-
-		var ignoreObjectSelectedSignal = false;
-
-		var outliner = new UI.Outliner( editor );
-		outliner.onChange( function () {
-
-			ignoreObjectSelectedSignal = true;
-
-			editor.history.goToState( parseInt( outliner.getValue() ) );
-
-			ignoreObjectSelectedSignal = false;
-
-		} );
-		container.add( outliner );
-
-		//
-
-		var refreshUI = function () {
-
-			var options = [];
-			var enumerator = 1;
-
-			function buildOption( object ) {
-
-				var option = document.createElement( 'div' );
-				option.value = object.id;
-
-				return option;
-
-			}
-
-			( function addObjects( objects ) {
-
-				for ( var i = 0, l = objects.length; i < l; i ++ ) {
-
-					var object = objects[ i ];
-
-					var option = buildOption( object );
-					option.innerHTML = '&nbsp;' + object.name;
-
-					options.push( option );
-
-				}
-
-			} )( history.undos );
-
-
-			( function addObjects( objects, pad ) {
-
-				for ( var i = objects.length - 1; i >= 0; i -- ) {
-
-					var object = objects[ i ];
-
-					var option = buildOption( object );
-					option.innerHTML = '&nbsp;' + object.name;
-					option.style.opacity = 0.3;
-
-					options.push( option );
-
-				}
-
-			} )( history.redos, '&nbsp;' );
-
-			outliner.setOptions( options );
-
-		};
-
-		refreshUI();
-
-		// events
-
-		signals.editorCleared.add( refreshUI );
-
-		signals.historyChanged.add( refreshUI );
-		signals.historyChanged.add( function ( cmd ) {
-
-			outliner.setValue( cmd !== undefined ? cmd.id : null );
-
-		} );
-
-
-		return container;
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Sidebar.History', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Sidebar.Material = function ( editor ) {
-
-		var signals = editor.signals;
-
-		var currentObject;
-
-		var currentMaterialSlot = 0;
-
-		var container = new UI.Panel();
-		container.setBorderTop( '0' );
-		container.setPaddingTop( '20px' );
-
-		// New / Copy / Paste
-
-		var copiedMaterial;
-
-		var managerRow = new UI.Row();
-
-		// Current material slot
-
-		var materialSlotRow = new UI.Row();
-
-		materialSlotRow.add( new UI.Text( 'Slot' ).setWidth( '90px' ) );
-
-		var materialSlotSelect = new UI.Select().setWidth( '170px' ).setFontSize( '12px' ).onChange( update );
-		materialSlotSelect.setOptions( { 0: '' } ).setValue( 0 );
-		materialSlotRow.add( materialSlotSelect );
-
-		container.add( materialSlotRow );
-
-		managerRow.add( new UI.Text( '' ).setWidth( '90px' ) );
-
-		managerRow.add( new UI.Button( 'New' ).onClick( function () {
-
-			var material = new THREE[ materialClass.getValue() ]();
-			editor.execute( new SetMaterialCommand( currentObject, material, currentMaterialSlot ), 'New Material: ' + materialClass.getValue() );
-			update();
-
-		} ) );
-
-		managerRow.add( new UI.Button( 'Copy' ).setMarginLeft( '4px' ).onClick( function () {
-
-			copiedMaterial = currentObject.material;
-
-			if ( Array.isArray( copiedMaterial ) ) {
-
-				if ( copiedMaterial.length === 0 ) return;
-
-				copiedMaterial = copiedMaterial[ currentMaterialSlot ];
-
-			}
-
-		} ) );
-
-		managerRow.add( new UI.Button( 'Paste' ).setMarginLeft( '4px' ).onClick( function () {
-
-			if ( copiedMaterial === undefined ) return;
-
-			editor.execute( new SetMaterialCommand( currentObject, copiedMaterial, currentMaterialSlot ), 'Pasted Material: ' + materialClass.getValue() );
-			refreshUI();
-			update();
-
-		} ) );
-
-		container.add( managerRow );
-
-
-		// type
-
-		var materialClassRow = new UI.Row();
-		var materialClass = new UI.Select().setOptions( {
-
-			'LineBasicMaterial': 'LineBasicMaterial',
-			'LineDashedMaterial': 'LineDashedMaterial',
-			'MeshBasicMaterial': 'MeshBasicMaterial',
-			'MeshDepthMaterial': 'MeshDepthMaterial',
-			'MeshNormalMaterial': 'MeshNormalMaterial',
-			'MeshLambertMaterial': 'MeshLambertMaterial',
-			'MeshPhongMaterial': 'MeshPhongMaterial',
-			'MeshStandardMaterial': 'MeshStandardMaterial',
-			'MeshPhysicalMaterial': 'MeshPhysicalMaterial',
-			'ShaderMaterial': 'ShaderMaterial',
-			'SpriteMaterial': 'SpriteMaterial'
-
-		} ).setWidth( '150px' ).setFontSize( '12px' ).onChange( update );
-
-		materialClassRow.add( new UI.Text( 'Type' ).setWidth( '90px' ) );
-		materialClassRow.add( materialClass );
-
-		container.add( materialClassRow );
-
-		// uuid
-
-		var materialUUIDRow = new UI.Row();
-		var materialUUID = new UI.Input().setWidth( '102px' ).setFontSize( '12px' ).setDisabled( true );
-		var materialUUIDRenew = new UI.Button( 'New' ).setMarginLeft( '7px' ).onClick( function () {
-
-			materialUUID.setValue( THREE.Math.generateUUID() );
-			update();
-
-		} );
-
-		materialUUIDRow.add( new UI.Text( 'UUID' ).setWidth( '90px' ) );
-		materialUUIDRow.add( materialUUID );
-		materialUUIDRow.add( materialUUIDRenew );
-
-		container.add( materialUUIDRow );
-
-		// name
-
-		var materialNameRow = new UI.Row();
-		var materialName = new UI.Input().setWidth( '150px' ).setFontSize( '12px' ).onChange( function () {
-
-			editor.execute( new SetMaterialValueCommand( editor.selected, 'name', materialName.getValue(), currentMaterialSlot ) );
-
-		} );
-
-		materialNameRow.add( new UI.Text( 'Name' ).setWidth( '90px' ) );
-		materialNameRow.add( materialName );
-
-		container.add( materialNameRow );
-
-		// program
-
-		var materialProgramRow = new UI.Row();
-		materialProgramRow.add( new UI.Text( 'Program' ).setWidth( '90px' ) );
-
-		var materialProgramInfo = new UI.Button( 'Info' );
-		materialProgramInfo.setMarginLeft( '4px' );
-		materialProgramInfo.onClick( function () {
-
-			signals.editScript.dispatch( currentObject, 'programInfo' );
-
-		} );
-		materialProgramRow.add( materialProgramInfo );
-
-		var materialProgramVertex = new UI.Button( 'Vertex' );
-		materialProgramVertex.setMarginLeft( '4px' );
-		materialProgramVertex.onClick( function () {
-
-			signals.editScript.dispatch( currentObject, 'vertexShader' );
-
-		} );
-		materialProgramRow.add( materialProgramVertex );
-
-		var materialProgramFragment = new UI.Button( 'Fragment' );
-		materialProgramFragment.setMarginLeft( '4px' );
-		materialProgramFragment.onClick( function () {
-
-			signals.editScript.dispatch( currentObject, 'fragmentShader' );
-
-		} );
-		materialProgramRow.add( materialProgramFragment );
-
-		container.add( materialProgramRow );
-
-		// color
-
-		var materialColorRow = new UI.Row();
-		var materialColor = new UI.Color().onChange( update );
-
-		materialColorRow.add( new UI.Text( 'Color' ).setWidth( '90px' ) );
-		materialColorRow.add( materialColor );
-
-		container.add( materialColorRow );
-
-		// roughness
-
-		var materialRoughnessRow = new UI.Row();
-		var materialRoughness = new UI.Number( 0.5 ).setWidth( '60px' ).setRange( 0, 1 ).onChange( update );
-
-		materialRoughnessRow.add( new UI.Text( 'Roughness' ).setWidth( '90px' ) );
-		materialRoughnessRow.add( materialRoughness );
-
-		container.add( materialRoughnessRow );
-
-		// metalness
-
-		var materialMetalnessRow = new UI.Row();
-		var materialMetalness = new UI.Number( 0.5 ).setWidth( '60px' ).setRange( 0, 1 ).onChange( update );
-
-		materialMetalnessRow.add( new UI.Text( 'Metalness' ).setWidth( '90px' ) );
-		materialMetalnessRow.add( materialMetalness );
-
-		container.add( materialMetalnessRow );
-
-		// emissive
-
-		var materialEmissiveRow = new UI.Row();
-		var materialEmissive = new UI.Color().setHexValue( 0x000000 ).onChange( update );
-
-		materialEmissiveRow.add( new UI.Text( 'Emissive' ).setWidth( '90px' ) );
-		materialEmissiveRow.add( materialEmissive );
-
-		container.add( materialEmissiveRow );
-
-		// specular
-
-		var materialSpecularRow = new UI.Row();
-		var materialSpecular = new UI.Color().setHexValue( 0x111111 ).onChange( update );
-
-		materialSpecularRow.add( new UI.Text( 'Specular' ).setWidth( '90px' ) );
-		materialSpecularRow.add( materialSpecular );
-
-		container.add( materialSpecularRow );
-
-		// shininess
-
-		var materialShininessRow = new UI.Row();
-		var materialShininess = new UI.Number( 30 ).onChange( update );
-
-		materialShininessRow.add( new UI.Text( 'Shininess' ).setWidth( '90px' ) );
-		materialShininessRow.add( materialShininess );
-
-		container.add( materialShininessRow );
-
-		// clearCoat
-
-		var materialClearCoatRow = new UI.Row();
-		var materialClearCoat = new UI.Number( 1 ).setWidth( '60px' ).setRange( 0, 1 ).onChange( update );
-
-		materialClearCoatRow.add( new UI.Text( 'ClearCoat' ).setWidth( '90px' ) );
-		materialClearCoatRow.add( materialClearCoat );
-
-		container.add( materialClearCoatRow );
-
-		// clearCoatRoughness
-
-		var materialClearCoatRoughnessRow = new UI.Row();
-		var materialClearCoatRoughness = new UI.Number( 1 ).setWidth( '60px' ).setRange( 0, 1 ).onChange( update );
-
-		materialClearCoatRoughnessRow.add( new UI.Text( 'ClearCoat Roughness' ).setWidth( '90px' ) );
-		materialClearCoatRoughnessRow.add( materialClearCoatRoughness );
-
-		container.add( materialClearCoatRoughnessRow );
-
-		// vertex colors
-
-		var materialVertexColorsRow = new UI.Row();
-		var materialVertexColors = new UI.Select().setOptions( {
-
-			0: 'No',
-			1: 'Face',
-			2: 'Vertex'
-
-		} ).onChange( update );
-
-		materialVertexColorsRow.add( new UI.Text( 'Vertex Colors' ).setWidth( '90px' ) );
-		materialVertexColorsRow.add( materialVertexColors );
-
-		container.add( materialVertexColorsRow );
-
-		// skinning
-
-		var materialSkinningRow = new UI.Row();
-		var materialSkinning = new UI.Checkbox( false ).onChange( update );
-
-		materialSkinningRow.add( new UI.Text( 'Skinning' ).setWidth( '90px' ) );
-		materialSkinningRow.add( materialSkinning );
-
-		container.add( materialSkinningRow );
-
-		// map
-
-		var materialMapRow = new UI.Row();
-		var materialMapEnabled = new UI.Checkbox( false ).onChange( update );
-		var materialMap = new UI.Texture().onChange( update );
-
-		materialMapRow.add( new UI.Text( 'Map' ).setWidth( '90px' ) );
-		materialMapRow.add( materialMapEnabled );
-		materialMapRow.add( materialMap );
-
-		container.add( materialMapRow );
-
-		// alpha map
-
-		var materialAlphaMapRow = new UI.Row();
-		var materialAlphaMapEnabled = new UI.Checkbox( false ).onChange( update );
-		var materialAlphaMap = new UI.Texture().onChange( update );
-
-		materialAlphaMapRow.add( new UI.Text( 'Alpha Map' ).setWidth( '90px' ) );
-		materialAlphaMapRow.add( materialAlphaMapEnabled );
-		materialAlphaMapRow.add( materialAlphaMap );
-
-		container.add( materialAlphaMapRow );
-
-		// bump map
-
-		var materialBumpMapRow = new UI.Row();
-		var materialBumpMapEnabled = new UI.Checkbox( false ).onChange( update );
-		var materialBumpMap = new UI.Texture().onChange( update );
-		var materialBumpScale = new UI.Number( 1 ).setWidth( '30px' ).onChange( update );
-
-		materialBumpMapRow.add( new UI.Text( 'Bump Map' ).setWidth( '90px' ) );
-		materialBumpMapRow.add( materialBumpMapEnabled );
-		materialBumpMapRow.add( materialBumpMap );
-		materialBumpMapRow.add( materialBumpScale );
-
-		container.add( materialBumpMapRow );
-
-		// normal map
-
-		var materialNormalMapRow = new UI.Row();
-		var materialNormalMapEnabled = new UI.Checkbox( false ).onChange( update );
-		var materialNormalMap = new UI.Texture().onChange( update );
-
-		materialNormalMapRow.add( new UI.Text( 'Normal Map' ).setWidth( '90px' ) );
-		materialNormalMapRow.add( materialNormalMapEnabled );
-		materialNormalMapRow.add( materialNormalMap );
-
-		container.add( materialNormalMapRow );
-
-		// displacement map
-
-		var materialDisplacementMapRow = new UI.Row();
-		var materialDisplacementMapEnabled = new UI.Checkbox( false ).onChange( update );
-		var materialDisplacementMap = new UI.Texture().onChange( update );
-		var materialDisplacementScale = new UI.Number( 1 ).setWidth( '30px' ).onChange( update );
-
-		materialDisplacementMapRow.add( new UI.Text( 'Displace Map' ).setWidth( '90px' ) );
-		materialDisplacementMapRow.add( materialDisplacementMapEnabled );
-		materialDisplacementMapRow.add( materialDisplacementMap );
-		materialDisplacementMapRow.add( materialDisplacementScale );
-
-		container.add( materialDisplacementMapRow );
-
-		// roughness map
-
-		var materialRoughnessMapRow = new UI.Row();
-		var materialRoughnessMapEnabled = new UI.Checkbox( false ).onChange( update );
-		var materialRoughnessMap = new UI.Texture().onChange( update );
-
-		materialRoughnessMapRow.add( new UI.Text( 'Rough. Map' ).setWidth( '90px' ) );
-		materialRoughnessMapRow.add( materialRoughnessMapEnabled );
-		materialRoughnessMapRow.add( materialRoughnessMap );
-
-		container.add( materialRoughnessMapRow );
-
-		// metalness map
-
-		var materialMetalnessMapRow = new UI.Row();
-		var materialMetalnessMapEnabled = new UI.Checkbox( false ).onChange( update );
-		var materialMetalnessMap = new UI.Texture().onChange( update );
-
-		materialMetalnessMapRow.add( new UI.Text( 'Metal. Map' ).setWidth( '90px' ) );
-		materialMetalnessMapRow.add( materialMetalnessMapEnabled );
-		materialMetalnessMapRow.add( materialMetalnessMap );
-
-		container.add( materialMetalnessMapRow );
-
-		// specular map
-
-		var materialSpecularMapRow = new UI.Row();
-		var materialSpecularMapEnabled = new UI.Checkbox( false ).onChange( update );
-		var materialSpecularMap = new UI.Texture().onChange( update );
-
-		materialSpecularMapRow.add( new UI.Text( 'Specular Map' ).setWidth( '90px' ) );
-		materialSpecularMapRow.add( materialSpecularMapEnabled );
-		materialSpecularMapRow.add( materialSpecularMap );
-
-		container.add( materialSpecularMapRow );
-
-		// env map
-
-		var materialEnvMapRow = new UI.Row();
-		var materialEnvMapEnabled = new UI.Checkbox( false ).onChange( update );
-		var materialEnvMap = new UI.Texture( THREE.SphericalReflectionMapping ).onChange( update );
-		var materialReflectivity = new UI.Number( 1 ).setWidth( '30px' ).onChange( update );
-
-		materialEnvMapRow.add( new UI.Text( 'Env Map' ).setWidth( '90px' ) );
-		materialEnvMapRow.add( materialEnvMapEnabled );
-		materialEnvMapRow.add( materialEnvMap );
-		materialEnvMapRow.add( materialReflectivity );
-
-		container.add( materialEnvMapRow );
-
-		// light map
-
-		var materialLightMapRow = new UI.Row();
-		var materialLightMapEnabled = new UI.Checkbox( false ).onChange( update );
-		var materialLightMap = new UI.Texture().onChange( update );
-
-		materialLightMapRow.add( new UI.Text( 'Light Map' ).setWidth( '90px' ) );
-		materialLightMapRow.add( materialLightMapEnabled );
-		materialLightMapRow.add( materialLightMap );
-
-		container.add( materialLightMapRow );
-
-		// ambient occlusion map
-
-		var materialAOMapRow = new UI.Row();
-		var materialAOMapEnabled = new UI.Checkbox( false ).onChange( update );
-		var materialAOMap = new UI.Texture().onChange( update );
-		var materialAOScale = new UI.Number( 1 ).setRange( 0, 1 ).setWidth( '30px' ).onChange( update );
-
-		materialAOMapRow.add( new UI.Text( 'AO Map' ).setWidth( '90px' ) );
-		materialAOMapRow.add( materialAOMapEnabled );
-		materialAOMapRow.add( materialAOMap );
-		materialAOMapRow.add( materialAOScale );
-
-		container.add( materialAOMapRow );
-
-		// emissive map
-
-		var materialEmissiveMapRow = new UI.Row();
-		var materialEmissiveMapEnabled = new UI.Checkbox( false ).onChange( update );
-		var materialEmissiveMap = new UI.Texture().onChange( update );
-
-		materialEmissiveMapRow.add( new UI.Text( 'Emissive Map' ).setWidth( '90px' ) );
-		materialEmissiveMapRow.add( materialEmissiveMapEnabled );
-		materialEmissiveMapRow.add( materialEmissiveMap );
-
-		container.add( materialEmissiveMapRow );
-
-		// side
-
-		var materialSideRow = new UI.Row();
-		var materialSide = new UI.Select().setOptions( {
-
-			0: 'Front',
-			1: 'Back',
-			2: 'Double'
-
-		} ).setWidth( '150px' ).setFontSize( '12px' ).onChange( update );
-
-		materialSideRow.add( new UI.Text( 'Side' ).setWidth( '90px' ) );
-		materialSideRow.add( materialSide );
-
-		container.add( materialSideRow );
-
-		// shading
-
-		var materialShadingRow = new UI.Row();
-		var materialShading = new UI.Checkbox(false).setLeft( '100px' ).onChange( update );
-
-		materialShadingRow.add( new UI.Text( 'Flat Shaded' ).setWidth( '90px' ) );
-		materialShadingRow.add( materialShading );
-
-		container.add( materialShadingRow );
-
-		// blending
-
-		var materialBlendingRow = new UI.Row();
-		var materialBlending = new UI.Select().setOptions( {
-
-			0: 'No',
-			1: 'Normal',
-			2: 'Additive',
-			3: 'Subtractive',
-			4: 'Multiply',
-			5: 'Custom'
-
-		} ).setWidth( '150px' ).setFontSize( '12px' ).onChange( update );
-
-		materialBlendingRow.add( new UI.Text( 'Blending' ).setWidth( '90px' ) );
-		materialBlendingRow.add( materialBlending );
-
-		container.add( materialBlendingRow );
-
-		// opacity
-
-		var materialOpacityRow = new UI.Row();
-		var materialOpacity = new UI.Number( 1 ).setWidth( '60px' ).setRange( 0, 1 ).onChange( update );
-
-		materialOpacityRow.add( new UI.Text( 'Opacity' ).setWidth( '90px' ) );
-		materialOpacityRow.add( materialOpacity );
-
-		container.add( materialOpacityRow );
-
-		// transparent
-
-		var materialTransparentRow = new UI.Row();
-		var materialTransparent = new UI.Checkbox().setLeft( '100px' ).onChange( update );
-
-		materialTransparentRow.add( new UI.Text( 'Transparent' ).setWidth( '90px' ) );
-		materialTransparentRow.add( materialTransparent );
-
-		container.add( materialTransparentRow );
-
-		// alpha test
-
-		var materialAlphaTestRow = new UI.Row();
-		var materialAlphaTest = new UI.Number().setWidth( '60px' ).setRange( 0, 1 ).onChange( update );
-
-		materialAlphaTestRow.add( new UI.Text( 'Alpha Test' ).setWidth( '90px' ) );
-		materialAlphaTestRow.add( materialAlphaTest );
-
-		container.add( materialAlphaTestRow );
-
-		// wireframe
-
-		var materialWireframeRow = new UI.Row();
-		var materialWireframe = new UI.Checkbox( false ).onChange( update );
-		var materialWireframeLinewidth = new UI.Number( 1 ).setWidth( '60px' ).setRange( 0, 100 ).onChange( update );
-
-		materialWireframeRow.add( new UI.Text( 'Wireframe' ).setWidth( '90px' ) );
-		materialWireframeRow.add( materialWireframe );
-		materialWireframeRow.add( materialWireframeLinewidth );
-
-		container.add( materialWireframeRow );
-
-		//
-
-		function update() {
-
-			var object = currentObject;
-
-			var geometry = object.geometry;
-
-			var previousSelectedSlot = currentMaterialSlot;
-
-			currentMaterialSlot = parseInt( materialSlotSelect.getValue() );
-
-			if ( currentMaterialSlot !== previousSelectedSlot ) refreshUI( true );
-
-			var material = editor.getObjectMaterial( currentObject, currentMaterialSlot );
-
-			var textureWarning = false;
-			var objectHasUvs = false;
-
-			if ( object instanceof THREE.Sprite ) objectHasUvs = true;
-			if ( geometry instanceof THREE.Geometry && geometry.faceVertexUvs[ 0 ].length > 0 ) objectHasUvs = true;
-			if ( geometry instanceof THREE.BufferGeometry && geometry.attributes.uv !== undefined ) objectHasUvs = true;
-
-			if ( material ) {
-
-				if ( material.uuid !== undefined && material.uuid !== materialUUID.getValue() ) {
-
-					editor.execute( new SetMaterialValueCommand( currentObject, 'uuid', materialUUID.getValue(), currentMaterialSlot ) );
-
-				}
-
-				if ( material instanceof THREE[ materialClass.getValue() ] === false ) {
-
-					material = new THREE[ materialClass.getValue() ]();
-
-					editor.execute( new SetMaterialCommand( currentObject, material, currentMaterialSlot ), 'New Material: ' + materialClass.getValue() );
-					// TODO Copy other references in the scene graph
-					// keeping name and UUID then.
-					// Also there should be means to create a unique
-					// copy for the current object explicitly and to
-					// attach the current material to other objects.
-
-				}
-
-				if ( material.color !== undefined && material.color.getHex() !== materialColor.getHexValue() ) {
-
-					editor.execute( new SetMaterialColorCommand( currentObject, 'color', materialColor.getHexValue(), currentMaterialSlot ) );
-
-				}
-
-				if ( material.roughness !== undefined && Math.abs( material.roughness - materialRoughness.getValue() ) >= 0.01 ) {
-
-					editor.execute( new SetMaterialValueCommand( currentObject, 'roughness', materialRoughness.getValue(), currentMaterialSlot ) );
-
-				}
-
-				if ( material.metalness !== undefined && Math.abs( material.metalness - materialMetalness.getValue() ) >= 0.01 ) {
-
-					editor.execute( new SetMaterialValueCommand( currentObject, 'metalness', materialMetalness.getValue(), currentMaterialSlot ) );
-
-				}
-
-				if ( material.emissive !== undefined && material.emissive.getHex() !== materialEmissive.getHexValue() ) {
-
-					editor.execute( new SetMaterialColorCommand( currentObject, 'emissive', materialEmissive.getHexValue(), currentMaterialSlot ) );
-
-				}
-
-				if ( material.specular !== undefined && material.specular.getHex() !== materialSpecular.getHexValue() ) {
-
-					editor.execute( new SetMaterialColorCommand( currentObject, 'specular', materialSpecular.getHexValue(), currentMaterialSlot ) );
-
-				}
-
-				if ( material.shininess !== undefined && Math.abs( material.shininess - materialShininess.getValue() ) >= 0.01 ) {
-
-					editor.execute( new SetMaterialValueCommand( currentObject, 'shininess', materialShininess.getValue(), currentMaterialSlot ) );
-
-				}
-
-				if ( material.clearCoat !== undefined && Math.abs( material.clearCoat - materialClearCoat.getValue() ) >= 0.01 ) {
-
-					editor.execute( new SetMaterialValueCommand( currentObject, 'clearCoat', materialClearCoat.getValue(), currentMaterialSlot ) );
-
-				}
-
-				if ( material.clearCoatRoughness !== undefined && Math.abs( material.clearCoatRoughness - materialClearCoatRoughness.getValue() ) >= 0.01 ) {
-
-					editor.execute( new SetMaterialValueCommand( currentObject, 'clearCoatRoughness', materialClearCoatRoughness.getValue(), currentMaterialSlot ) );
-
-				}
-
-				if ( material.vertexColors !== undefined ) {
-
-					var vertexColors = parseInt( materialVertexColors.getValue() );
-
-					if ( material.vertexColors !== vertexColors ) {
-
-						editor.execute( new SetMaterialValueCommand( currentObject, 'vertexColors', vertexColors, currentMaterialSlot ) );
-
-					}
-
-				}
-
-				if ( material.skinning !== undefined && material.skinning !== materialSkinning.getValue() ) {
-
-					editor.execute( new SetMaterialValueCommand( currentObject, 'skinning', materialSkinning.getValue(), currentMaterialSlot ) );
-
-				}
-
-				if ( material.map !== undefined ) {
-
-					var mapEnabled = materialMapEnabled.getValue() === true;
-
-					if ( objectHasUvs ) {
-
-						var map = mapEnabled ? materialMap.getValue() : null;
-						if ( material.map !== map ) {
-
-							editor.execute( new SetMaterialMapCommand( currentObject, 'map', map, currentMaterialSlot ) );
-
-						}
-
-					} else {
-
-						if ( mapEnabled ) textureWarning = true;
-
-					}
-
-				}
-
-				if ( material.alphaMap !== undefined ) {
-
-					var mapEnabled = materialAlphaMapEnabled.getValue() === true;
-
-					if ( objectHasUvs ) {
-
-						var alphaMap = mapEnabled ? materialAlphaMap.getValue() : null;
-						if ( material.alphaMap !== alphaMap ) {
-
-							editor.execute( new SetMaterialMapCommand( currentObject, 'alphaMap', alphaMap, currentMaterialSlot ) );
-
-						}
-
-					} else {
-
-						if ( mapEnabled ) textureWarning = true;
-
-					}
-
-				}
-
-				if ( material.bumpMap !== undefined ) {
-
-					var bumpMapEnabled = materialBumpMapEnabled.getValue() === true;
-
-					if ( objectHasUvs ) {
-
-						var bumpMap = bumpMapEnabled ? materialBumpMap.getValue() : null;
-						if ( material.bumpMap !== bumpMap ) {
-
-							editor.execute( new SetMaterialMapCommand( currentObject, 'bumpMap', bumpMap, currentMaterialSlot ) );
-
-						}
-
-						if ( material.bumpScale !== materialBumpScale.getValue() ) {
-
-							editor.execute( new SetMaterialValueCommand( currentObject, 'bumpScale', materialBumpScale.getValue(), currentMaterialSlot ) );
-
-						}
-
-					} else {
-
-						if ( bumpMapEnabled ) textureWarning = true;
-
-					}
-
-				}
-
-				if ( material.normalMap !== undefined ) {
-
-					var normalMapEnabled = materialNormalMapEnabled.getValue() === true;
-
-					if ( objectHasUvs ) {
-
-						var normalMap = normalMapEnabled ? materialNormalMap.getValue() : null;
-						if ( material.normalMap !== normalMap ) {
-
-							editor.execute( new SetMaterialMapCommand( currentObject, 'normalMap', normalMap, currentMaterialSlot ) );
-
-						}
-
-					} else {
-
-						if ( normalMapEnabled ) textureWarning = true;
-
-					}
-
-				}
-
-				if ( material.displacementMap !== undefined ) {
-
-					var displacementMapEnabled = materialDisplacementMapEnabled.getValue() === true;
-
-					if ( objectHasUvs ) {
-
-						var displacementMap = displacementMapEnabled ? materialDisplacementMap.getValue() : null;
-						if ( material.displacementMap !== displacementMap ) {
-
-							editor.execute( new SetMaterialMapCommand( currentObject, 'displacementMap', displacementMap, currentMaterialSlot ) );
-
-						}
-
-						if ( material.displacementScale !== materialDisplacementScale.getValue() ) {
-
-							editor.execute( new SetMaterialValueCommand( currentObject, 'displacementScale', materialDisplacementScale.getValue(), currentMaterialSlot ) );
-
-						}
-
-					} else {
-
-						if ( displacementMapEnabled ) textureWarning = true;
-
-					}
-
-				}
-
-				if ( material.roughnessMap !== undefined ) {
-
-					var roughnessMapEnabled = materialRoughnessMapEnabled.getValue() === true;
-
-					if ( objectHasUvs ) {
-
-						var roughnessMap = roughnessMapEnabled ? materialRoughnessMap.getValue() : null;
-						if ( material.roughnessMap !== roughnessMap ) {
-
-							editor.execute( new SetMaterialMapCommand( currentObject, 'roughnessMap', roughnessMap, currentMaterialSlot ) );
-
-						}
-
-					} else {
-
-						if ( roughnessMapEnabled ) textureWarning = true;
-
-					}
-
-				}
-
-				if ( material.metalnessMap !== undefined ) {
-
-					var metalnessMapEnabled = materialMetalnessMapEnabled.getValue() === true;
-
-					if ( objectHasUvs ) {
-
-						var metalnessMap = metalnessMapEnabled ? materialMetalnessMap.getValue() : null;
-						if ( material.metalnessMap !== metalnessMap ) {
-
-							editor.execute( new SetMaterialMapCommand( currentObject, 'metalnessMap', metalnessMap, currentMaterialSlot ) );
-
-						}
-
-					} else {
-
-						if ( metalnessMapEnabled ) textureWarning = true;
-
-					}
-
-				}
-
-				if ( material.specularMap !== undefined ) {
-
-					var specularMapEnabled = materialSpecularMapEnabled.getValue() === true;
-
-					if ( objectHasUvs ) {
-
-						var specularMap = specularMapEnabled ? materialSpecularMap.getValue() : null;
-						if ( material.specularMap !== specularMap ) {
-
-							editor.execute( new SetMaterialMapCommand( currentObject, 'specularMap', specularMap, currentMaterialSlot ) );
-
-						}
-
-					} else {
-
-						if ( specularMapEnabled ) textureWarning = true;
-
-					}
-
-				}
-
-				if ( material.envMap !== undefined ) {
-
-					var envMapEnabled = materialEnvMapEnabled.getValue() === true;
-
-					var envMap = envMapEnabled ? materialEnvMap.getValue() : null;
-
-					if ( material.envMap !== envMap ) {
-
-						editor.execute( new SetMaterialMapCommand( currentObject, 'envMap', envMap, currentMaterialSlot ) );
-
-					}
-
-				}
-
-				if ( material.reflectivity !== undefined ) {
-
-					var reflectivity = materialReflectivity.getValue();
-
-					if ( material.reflectivity !== reflectivity ) {
-
-						editor.execute( new SetMaterialValueCommand( currentObject, 'reflectivity', reflectivity, currentMaterialSlot ) );
-
-					}
-
-				}
-
-				if ( material.lightMap !== undefined ) {
-
-					var lightMapEnabled = materialLightMapEnabled.getValue() === true;
-
-					if ( objectHasUvs ) {
-
-						var lightMap = lightMapEnabled ? materialLightMap.getValue() : null;
-						if ( material.lightMap !== lightMap ) {
-
-							editor.execute( new SetMaterialMapCommand( currentObject, 'lightMap', lightMap, currentMaterialSlot ) );
-
-						}
-
-					} else {
-
-						if ( lightMapEnabled ) textureWarning = true;
-
-					}
-
-				}
-
-				if ( material.aoMap !== undefined ) {
-
-					var aoMapEnabled = materialAOMapEnabled.getValue() === true;
-
-					if ( objectHasUvs ) {
-
-						var aoMap = aoMapEnabled ? materialAOMap.getValue() : null;
-						if ( material.aoMap !== aoMap ) {
-
-							editor.execute( new SetMaterialMapCommand( currentObject, 'aoMap', aoMap, currentMaterialSlot ) );
-
-						}
-
-						if ( material.aoMapIntensity !== materialAOScale.getValue() ) {
-
-							editor.execute( new SetMaterialValueCommand( currentObject, 'aoMapIntensity', materialAOScale.getValue(), currentMaterialSlot ) );
-
-						}
-
-					} else {
-
-						if ( aoMapEnabled ) textureWarning = true;
-
-					}
-
-				}
-
-				if ( material.emissiveMap !== undefined ) {
-
-					var emissiveMapEnabled = materialEmissiveMapEnabled.getValue() === true;
-
-					if ( objectHasUvs ) {
-
-						var emissiveMap = emissiveMapEnabled ? materialEmissiveMap.getValue() : null;
-						if ( material.emissiveMap !== emissiveMap ) {
-
-							editor.execute( new SetMaterialMapCommand( currentObject, 'emissiveMap', emissiveMap, currentMaterialSlot ) );
-
-						}
-
-					} else {
-
-						if ( emissiveMapEnabled ) textureWarning = true;
-
-					}
-
-				}
-
-				if ( material.side !== undefined ) {
-
-					var side = parseInt( materialSide.getValue() );
-					if ( material.side !== side ) {
-
-						editor.execute( new SetMaterialValueCommand( currentObject, 'side', side, currentMaterialSlot ) );
-
-					}
-
-
-				}
-
-				if ( material.flatShading !== undefined ) {
-
-					var flatShading = materialShading.getValue();
-					if ( material.flatShading != flatShading ) {
-
-						editor.execute( new SetMaterialValueCommand( currentObject, 'flatShading', flatShading, currentMaterialSlot ) );
-
-					}
-
-				}
-
-				if ( material.blending !== undefined ) {
-
-					var blending = parseInt( materialBlending.getValue() );
-					if ( material.blending !== blending ) {
-
-						editor.execute( new SetMaterialValueCommand( currentObject, 'blending', blending, currentMaterialSlot ) );
-
-					}
-
-				}
-
-				if ( material.opacity !== undefined && Math.abs( material.opacity - materialOpacity.getValue() ) >= 0.01 ) {
-
-					editor.execute( new SetMaterialValueCommand( currentObject, 'opacity', materialOpacity.getValue(), currentMaterialSlot ) );
-
-				}
-
-				if ( material.transparent !== undefined && material.transparent !== materialTransparent.getValue() ) {
-
-					editor.execute( new SetMaterialValueCommand( currentObject, 'transparent', materialTransparent.getValue(), currentMaterialSlot ) );
-
-				}
-
-				if ( material.alphaTest !== undefined && Math.abs( material.alphaTest - materialAlphaTest.getValue() ) >= 0.01 ) {
-
-					editor.execute( new SetMaterialValueCommand( currentObject, 'alphaTest', materialAlphaTest.getValue(), currentMaterialSlot ) );
-
-				}
-
-				if ( material.wireframe !== undefined && material.wireframe !== materialWireframe.getValue() ) {
-
-					editor.execute( new SetMaterialValueCommand( currentObject, 'wireframe', materialWireframe.getValue(), currentMaterialSlot) );
-
-				}
-
-				if ( material.wireframeLinewidth !== undefined && Math.abs( material.wireframeLinewidth - materialWireframeLinewidth.getValue() ) >= 0.01 ) {
-
-					editor.execute( new SetMaterialValueCommand( currentObject, 'wireframeLinewidth', materialWireframeLinewidth.getValue(), currentMaterialSlot ) );
-
-				}
-
-				refreshUI();
-
-			}
-
-			if ( textureWarning ) {
-
-				console.warn( "Can't set texture, model doesn't have texture coordinates" );
-
-			}
-
-		}
-
-		//
-
-		function setRowVisibility() {
-
-			var properties = {
-				'name': materialNameRow,
-				'color': materialColorRow,
-				'roughness': materialRoughnessRow,
-				'metalness': materialMetalnessRow,
-				'emissive': materialEmissiveRow,
-				'specular': materialSpecularRow,
-				'shininess': materialShininessRow,
-				'clearCoat': materialClearCoatRow,
-				'clearCoatRoughness': materialClearCoatRoughnessRow,
-				'vertexShader': materialProgramRow,
-				'vertexColors': materialVertexColorsRow,
-				'skinning': materialSkinningRow,
-				'map': materialMapRow,
-				'alphaMap': materialAlphaMapRow,
-				'bumpMap': materialBumpMapRow,
-				'normalMap': materialNormalMapRow,
-				'displacementMap': materialDisplacementMapRow,
-				'roughnessMap': materialRoughnessMapRow,
-				'metalnessMap': materialMetalnessMapRow,
-				'specularMap': materialSpecularMapRow,
-				'envMap': materialEnvMapRow,
-				'lightMap': materialLightMapRow,
-				'aoMap': materialAOMapRow,
-				'emissiveMap': materialEmissiveMapRow,
-				'side': materialSideRow,
-				'flatShading': materialShadingRow,
-				'blending': materialBlendingRow,
-				'opacity': materialOpacityRow,
-				'transparent': materialTransparentRow,
-				'alphaTest': materialAlphaTestRow,
-				'wireframe': materialWireframeRow
-			};
-
-			var material = currentObject.material;
-
-			if ( Array.isArray( material ) ) {
-
-				materialSlotRow.setDisplay( '' );
-
-				if ( material.length === 0 ) return;
-
-				material = material[ currentMaterialSlot ];
-
-			} else {
-
-				materialSlotRow.setDisplay( 'none' );
-
-			}
-
-			for ( var property in properties ) {
-
-				properties[ property ].setDisplay( material[ property ] !== undefined ? '' : 'none' );
-
-			}
-
-		}
-
-
-		function refreshUI( resetTextureSelectors ) {
-
-			if ( ! currentObject ) return;
-
-			var material = currentObject.material;
-
-			if ( Array.isArray( material ) ) {
-
-				var slotOptions = {};
-
-				currentMaterialSlot = Math.max( 0, Math.min( material.length, currentMaterialSlot ) );
-
-				for ( var i = 0; i < material.length; i ++ ) {
-
-					slotOptions[ i ] = String( i + 1 ) + ': ' + material[ i ].name;
-
-				}
-
-				materialSlotSelect.setOptions( slotOptions ).setValue( currentMaterialSlot );
-
-			}
-
-			material = editor.getObjectMaterial( currentObject, currentMaterialSlot );
-
-			if ( material.uuid !== undefined ) {
-
-				materialUUID.setValue( material.uuid );
-
-			}
-
-			if ( material.name !== undefined ) {
-
-				materialName.setValue( material.name );
-
-			}
-
-			materialClass.setValue( material.type );
-
-			if ( material.color !== undefined ) {
-
-				materialColor.setHexValue( material.color.getHexString() );
-
-			}
-
-			if ( material.roughness !== undefined ) {
-
-				materialRoughness.setValue( material.roughness );
-
-			}
-
-			if ( material.metalness !== undefined ) {
-
-				materialMetalness.setValue( material.metalness );
-
-			}
-
-			if ( material.emissive !== undefined ) {
-
-				materialEmissive.setHexValue( material.emissive.getHexString() );
-
-			}
-
-			if ( material.specular !== undefined ) {
-
-				materialSpecular.setHexValue( material.specular.getHexString() );
-
-			}
-
-			if ( material.shininess !== undefined ) {
-
-				materialShininess.setValue( material.shininess );
-
-			}
-
-			if ( material.clearCoat !== undefined ) {
-
-				materialClearCoat.setValue( material.clearCoat );
-
-			}
-
-			if ( material.clearCoatRoughness !== undefined ) {
-
-				materialClearCoatRoughness.setValue( material.clearCoatRoughness );
-
-			}
-
-			if ( material.vertexColors !== undefined ) {
-
-				materialVertexColors.setValue( material.vertexColors );
-
-			}
-
-			if ( material.skinning !== undefined ) {
-
-				materialSkinning.setValue( material.skinning );
-
-			}
-
-			if ( material.map !== undefined ) {
-
-				materialMapEnabled.setValue( material.map !== null );
-
-				if ( material.map !== null || resetTextureSelectors ) {
-
-					materialMap.setValue( material.map );
-
-				}
-
-			}
-
-			if ( material.alphaMap !== undefined ) {
-
-				materialAlphaMapEnabled.setValue( material.alphaMap !== null );
-
-				if ( material.alphaMap !== null || resetTextureSelectors ) {
-
-					materialAlphaMap.setValue( material.alphaMap );
-
-				}
-
-			}
-
-			if ( material.bumpMap !== undefined ) {
-
-				materialBumpMapEnabled.setValue( material.bumpMap !== null );
-
-				if ( material.bumpMap !== null || resetTextureSelectors ) {
-
-					materialBumpMap.setValue( material.bumpMap );
-
-				}
-
-				materialBumpScale.setValue( material.bumpScale );
-
-			}
-
-			if ( material.normalMap !== undefined ) {
-
-				materialNormalMapEnabled.setValue( material.normalMap !== null );
-
-				if ( material.normalMap !== null || resetTextureSelectors ) {
-
-					materialNormalMap.setValue( material.normalMap );
-
-				}
-
-			}
-
-			if ( material.displacementMap !== undefined ) {
-
-				materialDisplacementMapEnabled.setValue( material.displacementMap !== null );
-
-				if ( material.displacementMap !== null || resetTextureSelectors ) {
-
-					materialDisplacementMap.setValue( material.displacementMap );
-
-				}
-
-				materialDisplacementScale.setValue( material.displacementScale );
-
-			}
-
-			if ( material.roughnessMap !== undefined ) {
-
-				materialRoughnessMapEnabled.setValue( material.roughnessMap !== null );
-
-				if ( material.roughnessMap !== null || resetTextureSelectors ) {
-
-					materialRoughnessMap.setValue( material.roughnessMap );
-
-				}
-
-			}
-
-			if ( material.metalnessMap !== undefined ) {
-
-				materialMetalnessMapEnabled.setValue( material.metalnessMap !== null );
-
-				if ( material.metalnessMap !== null || resetTextureSelectors ) {
-
-					materialMetalnessMap.setValue( material.metalnessMap );
-
-				}
-
-			}
-
-			if ( material.specularMap !== undefined ) {
-
-				materialSpecularMapEnabled.setValue( material.specularMap !== null );
-
-				if ( material.specularMap !== null || resetTextureSelectors ) {
-
-					materialSpecularMap.setValue( material.specularMap );
-
-				}
-
-			}
-
-			if ( material.envMap !== undefined ) {
-
-				materialEnvMapEnabled.setValue( material.envMap !== null );
-
-				if ( material.envMap !== null || resetTextureSelectors ) {
-
-					materialEnvMap.setValue( material.envMap );
-
-				}
-
-			}
-
-			if ( material.reflectivity !== undefined ) {
-
-				materialReflectivity.setValue( material.reflectivity );
-
-			}
-
-			if ( material.lightMap !== undefined ) {
-
-				materialLightMapEnabled.setValue( material.lightMap !== null );
-
-				if ( material.lightMap !== null || resetTextureSelectors ) {
-
-					materialLightMap.setValue( material.lightMap );
-
-				}
-
-			}
-
-			if ( material.aoMap !== undefined ) {
-
-				materialAOMapEnabled.setValue( material.aoMap !== null );
-
-				if ( material.aoMap !== null || resetTextureSelectors ) {
-
-					materialAOMap.setValue( material.aoMap );
-
-				}
-
-				materialAOScale.setValue( material.aoMapIntensity );
-
-			}
-
-			if ( material.emissiveMap !== undefined ) {
-
-				materialEmissiveMapEnabled.setValue( material.emissiveMap !== null );
-
-				if ( material.emissiveMap !== null || resetTextureSelectors ) {
-
-					materialEmissiveMap.setValue( material.emissiveMap );
-
-				}
-
-			}
-
-			if ( material.side !== undefined ) {
-
-				materialSide.setValue( material.side );
-
-			}
-
-			if ( material.flatShading !== undefined ) {
-
-				materialShading.setValue( material.flatShading );
-
-			}
-
-			if ( material.blending !== undefined ) {
-
-				materialBlending.setValue( material.blending );
-
-			}
-
-			if ( material.opacity !== undefined ) {
-
-				materialOpacity.setValue( material.opacity );
-
-			}
-
-			if ( material.transparent !== undefined ) {
-
-				materialTransparent.setValue( material.transparent );
-
-			}
-
-			if ( material.alphaTest !== undefined ) {
-
-				materialAlphaTest.setValue( material.alphaTest );
-
-			}
-
-			if ( material.wireframe !== undefined ) {
-
-				materialWireframe.setValue( material.wireframe );
-
-			}
-
-			if ( material.wireframeLinewidth !== undefined ) {
-
-				materialWireframeLinewidth.setValue( material.wireframeLinewidth );
-
-			}
-
-			setRowVisibility();
-
-		}
-
-		// events
-
-		signals.objectSelected.add( function ( object ) {
-
-			var hasMaterial = false;
-
-			if ( object && object.material ) {
-
-				hasMaterial = true;
-
-				if ( Array.isArray( object.material ) && object.material.length === 0 ) {
-
-					hasMaterial = false;
-
-				}
-
-			}
-
-			if ( hasMaterial ) {
-
-				var objectChanged = object !== currentObject;
-
-				currentObject = object;
-				refreshUI( objectChanged );
-				container.setDisplay( '' );
-
-			} else {
-
-				currentObject = null;
-				container.setDisplay( 'none' );
-
-			}
-
-		} );
-
-		signals.materialChanged.add( function () {
-
-			refreshUI();
-
-		} );
-
-		return container;
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Sidebar.Material', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Sidebar.Object = function ( editor ) {
-
-		var signals = editor.signals;
-
-		var container = new UI.Panel();
-		container.setBorderTop( '0' );
-		container.setPaddingTop( '20px' );
-		container.setDisplay( 'none' );
-
-		// Actions
-
-		var objectActions = new UI.Select().setPosition( 'absolute' ).setRight( '8px' ).setFontSize( '11px' );
-		objectActions.setOptions( {
-
-			'Actions': 'Actions',
-			'Reset Position': 'Reset Position',
-			'Reset Rotation': 'Reset Rotation',
-			'Reset Scale': 'Reset Scale'
-
-		} );
-		objectActions.onClick( function ( event ) {
-
-			event.stopPropagation(); // Avoid panel collapsing
-
-		} );
-		objectActions.onChange( function ( event ) {
-
-			var object = editor.selected;
-
-			switch ( this.getValue() ) {
-
-				case 'Reset Position':
-					editor.execute( new SetPositionCommand( object, new THREE.Vector3( 0, 0, 0 ) ) );
-					break;
-
-				case 'Reset Rotation':
-					editor.execute( new SetRotationCommand( object, new THREE.Euler( 0, 0, 0 ) ) );
-					break;
-
-				case 'Reset Scale':
-					editor.execute( new SetScaleCommand( object, new THREE.Vector3( 1, 1, 1 ) ) );
-					break;
-
-			}
-
-			this.setValue( 'Actions' );
-
-		} );
-		// container.addStatic( objectActions );
-
-		// type
-
-		var objectTypeRow = new UI.Row();
-		var objectType = new UI.Text();
-
-		objectTypeRow.add( new UI.Text( 'Type' ).setWidth( '90px' ) );
-		objectTypeRow.add( objectType );
-
-		container.add( objectTypeRow );
-
-		// uuid
-
-		var objectUUIDRow = new UI.Row();
-		var objectUUID = new UI.Input().setWidth( '102px' ).setFontSize( '12px' ).setDisabled( true );
-		var objectUUIDRenew = new UI.Button( 'New' ).setMarginLeft( '7px' ).onClick( function () {
-
-			objectUUID.setValue( THREE.Math.generateUUID() );
-
-			editor.execute( new SetUuidCommand( editor.selected, objectUUID.getValue() ) );
-
-		} );
-
-		objectUUIDRow.add( new UI.Text( 'UUID' ).setWidth( '90px' ) );
-		objectUUIDRow.add( objectUUID );
-		objectUUIDRow.add( objectUUIDRenew );
-
-		container.add( objectUUIDRow );
-
-		// name
-
-		var objectNameRow = new UI.Row();
-		var objectName = new UI.Input().setWidth( '150px' ).setFontSize( '12px' ).onChange( function () {
-
-			editor.execute( new SetValueCommand( editor.selected, 'name', objectName.getValue() ) );
-
-		} );
-
-		objectNameRow.add( new UI.Text( 'Name' ).setWidth( '90px' ) );
-		objectNameRow.add( objectName );
-
-		container.add( objectNameRow );
-
-		// position
-
-		var objectPositionRow = new UI.Row();
-		var objectPositionX = new UI.Number().setWidth( '50px' ).onChange( update );
-		var objectPositionY = new UI.Number().setWidth( '50px' ).onChange( update );
-		var objectPositionZ = new UI.Number().setWidth( '50px' ).onChange( update );
-
-		objectPositionRow.add( new UI.Text( 'Position' ).setWidth( '90px' ) );
-		objectPositionRow.add( objectPositionX, objectPositionY, objectPositionZ );
-
-		container.add( objectPositionRow );
-
-		// rotation
-
-		var objectRotationRow = new UI.Row();
-		var objectRotationX = new UI.Number().setStep( 10 ).setUnit( '°' ).setWidth( '50px' ).onChange( update );
-		var objectRotationY = new UI.Number().setStep( 10 ).setUnit( '°' ).setWidth( '50px' ).onChange( update );
-		var objectRotationZ = new UI.Number().setStep( 10 ).setUnit( '°' ).setWidth( '50px' ).onChange( update );
-
-		objectRotationRow.add( new UI.Text( 'Rotation' ).setWidth( '90px' ) );
-		objectRotationRow.add( objectRotationX, objectRotationY, objectRotationZ );
-
-		container.add( objectRotationRow );
-
-		// scale
-
-		var objectScaleRow = new UI.Row();
-		var objectScaleLock = new UI.Checkbox( true ).setPosition( 'absolute' ).setLeft( '75px' );
-		var objectScaleX = new UI.Number( 1 ).setRange( 0.01, Infinity ).setWidth( '50px' ).onChange( updateScaleX );
-		var objectScaleY = new UI.Number( 1 ).setRange( 0.01, Infinity ).setWidth( '50px' ).onChange( updateScaleY );
-		var objectScaleZ = new UI.Number( 1 ).setRange( 0.01, Infinity ).setWidth( '50px' ).onChange( updateScaleZ );
-
-		objectScaleRow.add( new UI.Text( 'Scale' ).setWidth( '90px' ) );
-		objectScaleRow.add( objectScaleLock );
-		objectScaleRow.add( objectScaleX, objectScaleY, objectScaleZ );
-
-		container.add( objectScaleRow );
-
-		// fov
-
-		var objectFovRow = new UI.Row();
-		var objectFov = new UI.Number().onChange( update );
-
-		objectFovRow.add( new UI.Text( 'Fov' ).setWidth( '90px' ) );
-		objectFovRow.add( objectFov );
-
-		container.add( objectFovRow );
-
-		// near
-
-		var objectNearRow = new UI.Row();
-		var objectNear = new UI.Number().onChange( update );
-
-		objectNearRow.add( new UI.Text( 'Near' ).setWidth( '90px' ) );
-		objectNearRow.add( objectNear );
-
-		container.add( objectNearRow );
-
-		// far
-
-		var objectFarRow = new UI.Row();
-		var objectFar = new UI.Number().onChange( update );
-
-		objectFarRow.add( new UI.Text( 'Far' ).setWidth( '90px' ) );
-		objectFarRow.add( objectFar );
-
-		container.add( objectFarRow );
-
-		// intensity
-
-		var objectIntensityRow = new UI.Row();
-		var objectIntensity = new UI.Number().setRange( 0, Infinity ).onChange( update );
-
-		objectIntensityRow.add( new UI.Text( 'Intensity' ).setWidth( '90px' ) );
-		objectIntensityRow.add( objectIntensity );
-
-		container.add( objectIntensityRow );
-
-		// color
-
-		var objectColorRow = new UI.Row();
-		var objectColor = new UI.Color().onChange( update );
-
-		objectColorRow.add( new UI.Text( 'Color' ).setWidth( '90px' ) );
-		objectColorRow.add( objectColor );
-
-		container.add( objectColorRow );
-
-		// ground color
-
-		var objectGroundColorRow = new UI.Row();
-		var objectGroundColor = new UI.Color().onChange( update );
-
-		objectGroundColorRow.add( new UI.Text( 'Ground color' ).setWidth( '90px' ) );
-		objectGroundColorRow.add( objectGroundColor );
-
-		container.add( objectGroundColorRow );
-
-		// distance
-
-		var objectDistanceRow = new UI.Row();
-		var objectDistance = new UI.Number().setRange( 0, Infinity ).onChange( update );
-
-		objectDistanceRow.add( new UI.Text( 'Distance' ).setWidth( '90px' ) );
-		objectDistanceRow.add( objectDistance );
-
-		container.add( objectDistanceRow );
-
-		// angle
-
-		var objectAngleRow = new UI.Row();
-		var objectAngle = new UI.Number().setPrecision( 3 ).setRange( 0, Math.PI / 2 ).onChange( update );
-
-		objectAngleRow.add( new UI.Text( 'Angle' ).setWidth( '90px' ) );
-		objectAngleRow.add( objectAngle );
-
-		container.add( objectAngleRow );
-
-		// penumbra
-
-		var objectPenumbraRow = new UI.Row();
-		var objectPenumbra = new UI.Number().setRange( 0, 1 ).onChange( update );
-
-		objectPenumbraRow.add( new UI.Text( 'Penumbra' ).setWidth( '90px' ) );
-		objectPenumbraRow.add( objectPenumbra );
-
-		container.add( objectPenumbraRow );
-
-		// decay
-
-		var objectDecayRow = new UI.Row();
-		var objectDecay = new UI.Number().setRange( 0, Infinity ).onChange( update );
-
-		objectDecayRow.add( new UI.Text( 'Decay' ).setWidth( '90px' ) );
-		objectDecayRow.add( objectDecay );
-
-		container.add( objectDecayRow );
-
-		// shadow
-
-		var objectShadowRow = new UI.Row();
-
-		objectShadowRow.add( new UI.Text( 'Shadow' ).setWidth( '90px' ) );
-
-		var objectCastShadow = new UI.THREE.Boolean( false, 'cast' ).onChange( update );
-		objectShadowRow.add( objectCastShadow );
-
-		var objectReceiveShadow = new UI.THREE.Boolean( false, 'receive' ).onChange( update );
-		objectShadowRow.add( objectReceiveShadow );
-
-		var objectShadowRadius = new UI.Number( 1 ).onChange( update );
-		objectShadowRow.add( objectShadowRadius );
-
-		container.add( objectShadowRow );
-
-		// visible
-
-		var objectVisibleRow = new UI.Row();
-		var objectVisible = new UI.Checkbox().onChange( update );
-
-		objectVisibleRow.add( new UI.Text( 'Visible' ).setWidth( '90px' ) );
-		objectVisibleRow.add( objectVisible );
-
-		container.add( objectVisibleRow );
-
-		// user data
-
-		var timeout;
-
-		var objectUserDataRow = new UI.Row();
-		var objectUserData = new UI.TextArea().setWidth( '150px' ).setHeight( '40px' ).setFontSize( '12px' ).onChange( update );
-		objectUserData.onKeyUp( function () {
-
-			try {
-
-				JSON.parse( objectUserData.getValue() );
-
-				objectUserData.dom.classList.add( 'success' );
-				objectUserData.dom.classList.remove( 'fail' );
-
-			} catch ( error ) {
-
-				objectUserData.dom.classList.remove( 'success' );
-				objectUserData.dom.classList.add( 'fail' );
-
-			}
-
-		} );
-
-		objectUserDataRow.add( new UI.Text( 'User data' ).setWidth( '90px' ) );
-		objectUserDataRow.add( objectUserData );
-
-		container.add( objectUserDataRow );
-
-
-		//
-
-		function updateScaleX() {
-
-			var object = editor.selected;
-
-			if ( objectScaleLock.getValue() === true ) {
-
-				var scale = objectScaleX.getValue() / object.scale.x;
-
-				objectScaleY.setValue( objectScaleY.getValue() * scale );
-				objectScaleZ.setValue( objectScaleZ.getValue() * scale );
-
-			}
-
-			update();
-
-		}
-
-		function updateScaleY() {
-
-			var object = editor.selected;
-
-			if ( objectScaleLock.getValue() === true ) {
-
-				var scale = objectScaleY.getValue() / object.scale.y;
-
-				objectScaleX.setValue( objectScaleX.getValue() * scale );
-				objectScaleZ.setValue( objectScaleZ.getValue() * scale );
-
-			}
-
-			update();
-
-		}
-
-		function updateScaleZ() {
-
-			var object = editor.selected;
-
-			if ( objectScaleLock.getValue() === true ) {
-
-				var scale = objectScaleZ.getValue() / object.scale.z;
-
-				objectScaleX.setValue( objectScaleX.getValue() * scale );
-				objectScaleY.setValue( objectScaleY.getValue() * scale );
-
-			}
-
-			update();
-
-		}
-
-		function update() {
-
-			var object = editor.selected;
-
-			if ( object !== null ) {
-
-				var newPosition = new THREE.Vector3( objectPositionX.getValue(), objectPositionY.getValue(), objectPositionZ.getValue() );
-				if ( object.position.distanceTo( newPosition ) >= 0.01 ) {
-
-					editor.execute( new SetPositionCommand( object, newPosition ) );
-
-				}
-
-				var newRotation = new THREE.Euler( objectRotationX.getValue() * THREE.Math.DEG2RAD, objectRotationY.getValue() * THREE.Math.DEG2RAD, objectRotationZ.getValue() * THREE.Math.DEG2RAD );
-				if ( object.rotation.toVector3().distanceTo( newRotation.toVector3() ) >= 0.01 ) {
-
-					editor.execute( new SetRotationCommand( object, newRotation ) );
-
-				}
-
-				var newScale = new THREE.Vector3( objectScaleX.getValue(), objectScaleY.getValue(), objectScaleZ.getValue() );
-				if ( object.scale.distanceTo( newScale ) >= 0.01 ) {
-
-					editor.execute( new SetScaleCommand( object, newScale ) );
-
-				}
-
-				if ( object.fov !== undefined && Math.abs( object.fov - objectFov.getValue() ) >= 0.01 ) {
-
-					editor.execute( new SetValueCommand( object, 'fov', objectFov.getValue() ) );
-					object.updateProjectionMatrix();
-
-				}
-
-				if ( object.near !== undefined && Math.abs( object.near - objectNear.getValue() ) >= 0.01 ) {
-
-					editor.execute( new SetValueCommand( object, 'near', objectNear.getValue() ) );
-
-				}
-
-				if ( object.far !== undefined && Math.abs( object.far - objectFar.getValue() ) >= 0.01 ) {
-
-					editor.execute( new SetValueCommand( object, 'far', objectFar.getValue() ) );
-
-				}
-
-				if ( object.intensity !== undefined && Math.abs( object.intensity - objectIntensity.getValue() ) >= 0.01 ) {
-
-					editor.execute( new SetValueCommand( object, 'intensity', objectIntensity.getValue() ) );
-
-				}
-
-				if ( object.color !== undefined && object.color.getHex() !== objectColor.getHexValue() ) {
-
-					editor.execute( new SetColorCommand( object, 'color', objectColor.getHexValue() ) );
-
-				}
-
-				if ( object.groundColor !== undefined && object.groundColor.getHex() !== objectGroundColor.getHexValue() ) {
-
-					editor.execute( new SetColorCommand( object, 'groundColor', objectGroundColor.getHexValue() ) );
-
-				}
-
-				if ( object.distance !== undefined && Math.abs( object.distance - objectDistance.getValue() ) >= 0.01 ) {
-
-					editor.execute( new SetValueCommand( object, 'distance', objectDistance.getValue() ) );
-
-				}
-
-				if ( object.angle !== undefined && Math.abs( object.angle - objectAngle.getValue() ) >= 0.01 ) {
-
-					editor.execute( new SetValueCommand( object, 'angle', objectAngle.getValue() ) );
-
-				}
-
-				if ( object.penumbra !== undefined && Math.abs( object.penumbra - objectPenumbra.getValue() ) >= 0.01 ) {
-
-					editor.execute( new SetValueCommand( object, 'penumbra', objectPenumbra.getValue() ) );
-
-				}
-
-				if ( object.decay !== undefined && Math.abs( object.decay - objectDecay.getValue() ) >= 0.01 ) {
-
-					editor.execute( new SetValueCommand( object, 'decay', objectDecay.getValue() ) );
-
-				}
-
-				if ( object.visible !== objectVisible.getValue() ) {
-
-					editor.execute( new SetValueCommand( object, 'visible', objectVisible.getValue() ) );
-
-				}
-
-				if ( object.castShadow !== undefined && object.castShadow !== objectCastShadow.getValue() ) {
-
-					editor.execute( new SetValueCommand( object, 'castShadow', objectCastShadow.getValue() ) );
-
-				}
-
-				if ( object.receiveShadow !== undefined && object.receiveShadow !== objectReceiveShadow.getValue() ) {
-
-					editor.execute( new SetValueCommand( object, 'receiveShadow', objectReceiveShadow.getValue() ) );
-					object.material.needsUpdate = true;
-
-				}
-
-				if ( object.shadow !== undefined ) {
-
-					if ( object.shadow.radius !== objectShadowRadius.getValue() ) {
-
-						editor.execute( new SetValueCommand( object.shadow, 'radius', objectShadowRadius.getValue() ) );
-
-					}
-
-				}
-
-				try {
-
-					var userData = JSON.parse( objectUserData.getValue() );
-					if ( JSON.stringify( object.userData ) != JSON.stringify( userData ) ) {
-
-						editor.execute( new SetValueCommand( object, 'userData', userData ) );
-
-					}
-
-				} catch ( exception ) {
-
-					console.warn( exception );
-
-				}
-
-			}
-
-		}
-
-		function updateRows( object ) {
-
-			var properties = {
-				'fov': objectFovRow,
-				'near': objectNearRow,
-				'far': objectFarRow,
-				'intensity': objectIntensityRow,
-				'color': objectColorRow,
-				'groundColor': objectGroundColorRow,
-				'distance' : objectDistanceRow,
-				'angle' : objectAngleRow,
-				'penumbra' : objectPenumbraRow,
-				'decay' : objectDecayRow,
-				'castShadow' : objectShadowRow,
-				'receiveShadow' : objectReceiveShadow,
-				'shadow': objectShadowRadius
-			};
-
-			for ( var property in properties ) {
-
-				properties[ property ].setDisplay( object[ property ] !== undefined ? '' : 'none' );
-
-			}
-
-		}
-
-		function updateTransformRows( object ) {
-
-			if ( object instanceof THREE.Light ||
-			   ( object instanceof THREE.Object3D && object.userData.targetInverse ) ) {
-
-				objectRotationRow.setDisplay( 'none' );
-				objectScaleRow.setDisplay( 'none' );
-
-			} else {
-
-				objectRotationRow.setDisplay( '' );
-				objectScaleRow.setDisplay( '' );
-
-			}
-
-		}
-
-		// events
-
-		signals.objectSelected.add( function ( object ) {
-
-			if ( object !== null ) {
-
-				container.setDisplay( 'block' );
-
-				updateRows( object );
-				updateUI( object );
-
-			} else {
-
-				container.setDisplay( 'none' );
-
-			}
-
-		} );
-
-		signals.objectChanged.add( function ( object ) {
-
-			if ( object !== editor.selected ) return;
-
-			updateUI( object );
-
-		} );
-
-		signals.refreshSidebarObject3D.add( function ( object ) {
-
-			if ( object !== editor.selected ) return;
-
-			updateUI( object );
-
-		} );
-
-		function updateUI( object ) {
-
-			objectType.setValue( object.type );
-
-			objectUUID.setValue( object.uuid );
-			objectName.setValue( object.name );
-
-			objectPositionX.setValue( object.position.x );
-			objectPositionY.setValue( object.position.y );
-			objectPositionZ.setValue( object.position.z );
-
-			objectRotationX.setValue( object.rotation.x * THREE.Math.RAD2DEG );
-			objectRotationY.setValue( object.rotation.y * THREE.Math.RAD2DEG );
-			objectRotationZ.setValue( object.rotation.z * THREE.Math.RAD2DEG );
-
-			objectScaleX.setValue( object.scale.x );
-			objectScaleY.setValue( object.scale.y );
-			objectScaleZ.setValue( object.scale.z );
-
-			if ( object.fov !== undefined ) {
-
-				objectFov.setValue( object.fov );
-
-			}
-
-			if ( object.near !== undefined ) {
-
-				objectNear.setValue( object.near );
-
-			}
-
-			if ( object.far !== undefined ) {
-
-				objectFar.setValue( object.far );
-
-			}
-
-			if ( object.intensity !== undefined ) {
-
-				objectIntensity.setValue( object.intensity );
-
-			}
-
-			if ( object.color !== undefined ) {
-
-				objectColor.setHexValue( object.color.getHexString() );
-
-			}
-
-			if ( object.groundColor !== undefined ) {
-
-				objectGroundColor.setHexValue( object.groundColor.getHexString() );
-
-			}
-
-			if ( object.distance !== undefined ) {
-
-				objectDistance.setValue( object.distance );
-
-			}
-
-			if ( object.angle !== undefined ) {
-
-				objectAngle.setValue( object.angle );
-
-			}
-
-			if ( object.penumbra !== undefined ) {
-
-				objectPenumbra.setValue( object.penumbra );
-
-			}
-
-			if ( object.decay !== undefined ) {
-
-				objectDecay.setValue( object.decay );
-
-			}
-
-			if ( object.castShadow !== undefined ) {
-
-				objectCastShadow.setValue( object.castShadow );
-
-			}
-
-			if ( object.receiveShadow !== undefined ) {
-
-				objectReceiveShadow.setValue( object.receiveShadow );
-
-			}
-
-			if ( object.shadow !== undefined ) {
-
-				objectShadowRadius.setValue( object.shadow.radius );
-
-			}
-
-			objectVisible.setValue( object.visible );
-
-			try {
-
-				objectUserData.setValue( JSON.stringify( object.userData, null, '  ' ) );
-
-			} catch ( error ) {
-
-				console.log( error );
-
-			}
-
-			objectUserData.setBorderColor( 'transparent' );
-			objectUserData.setBackgroundColor( '' );
-
-			updateTransformRows( object );
-
-		}
-
-		return container;
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Sidebar.Object', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Sidebar.Project = function ( editor ) {
-
-		var config = editor.config;
-		var signals = editor.signals;
-
-		var rendererTypes = {
-
-			'WebGLRenderer': THREE.WebGLRenderer,
-			'CanvasRenderer': THREE.CanvasRenderer,
-			'SVGRenderer': THREE.SVGRenderer,
-			'SoftwareRenderer': THREE.SoftwareRenderer,
-			'RaytracingRenderer': THREE.RaytracingRenderer
-
-		};
-
-		var container = new UI.Panel();
-		container.setBorderTop( '0' );
-		container.setPaddingTop( '20px' );
-
-		// class
-
-		var options = {};
-
-		for ( var key in rendererTypes ) {
-
-			if ( key.indexOf( 'WebGL' ) >= 0 && System.support.webgl === false ) continue;
-
-			options[ key ] = key;
-
-		}
-
-		var rendererTypeRow = new UI.Row();
-		var rendererType = new UI.Select().setOptions( options ).setWidth( '150px' ).onChange( function () {
-
-			var value = this.getValue();
-
-			config.setKey( 'project/renderer', value );
-
-			updateRenderer();
-
-		} );
-
-		rendererTypeRow.add( new UI.Text( 'Renderer' ).setWidth( '90px' ) );
-		rendererTypeRow.add( rendererType );
-
-		container.add( rendererTypeRow );
-
-		if ( config.getKey( 'project/renderer' ) !== undefined ) {
-
-			rendererType.setValue( config.getKey( 'project/renderer' ) );
-
-		}
-
-		// antialiasing
-
-		var rendererPropertiesRow = new UI.Row().setMarginLeft( '90px' );
-
-		var rendererAntialias = new UI.THREE.Boolean( config.getKey( 'project/renderer/antialias' ), 'antialias' ).onChange( function () {
-
-			config.setKey( 'project/renderer/antialias', this.getValue() );
-			updateRenderer();
-
-		} );
-		rendererPropertiesRow.add( rendererAntialias );
-
-		// shadow
-
-		var rendererShadows = new UI.THREE.Boolean( config.getKey( 'project/renderer/shadows' ), 'shadows' ).onChange( function () {
-
-			config.setKey( 'project/renderer/shadows', this.getValue() );
-			updateRenderer();
-
-		} );
-		rendererPropertiesRow.add( rendererShadows );
-
-		rendererPropertiesRow.add( new UI.Break() );
-
-		// gamma input
-
-		var rendererGammaInput = new UI.THREE.Boolean( config.getKey( 'project/renderer/gammaInput' ), 'γ input' ).onChange( function () {
-
-			config.setKey( 'project/renderer/gammaInput', this.getValue() );
-			updateRenderer();
-
-		} );
-		rendererPropertiesRow.add( rendererGammaInput );
-
-		// gamma output
-
-		var rendererGammaOutput = new UI.THREE.Boolean( config.getKey( 'project/renderer/gammaOutput' ), 'γ output' ).onChange( function () {
-
-			config.setKey( 'project/renderer/gammaOutput', this.getValue() );
-			updateRenderer();
-
-		} );
-		rendererPropertiesRow.add( rendererGammaOutput );
-
-		container.add( rendererPropertiesRow );
-
-		// Editable
-
-		var editableRow = new UI.Row();
-		var editable = new UI.Checkbox( config.getKey( 'project/editable' ) ).setLeft( '100px' ).onChange( function () {
-
-			config.setKey( 'project/editable', this.getValue() );
-
-		} );
-
-		editableRow.add( new UI.Text( 'Editable' ).setWidth( '90px' ) );
-		editableRow.add( editable );
-
-		container.add( editableRow );
-
-		// VR
-
-		var vrRow = new UI.Row();
-		var vr = new UI.Checkbox( config.getKey( 'project/vr' ) ).setLeft( '100px' ).onChange( function () {
-
-			config.setKey( 'project/vr', this.getValue() );
-
-		} );
-
-		vrRow.add( new UI.Text( 'VR' ).setWidth( '90px' ) );
-		vrRow.add( vr );
-
-		container.add( vrRow );
-
-		//
-
-		function updateRenderer() {
-
-			createRenderer( rendererType.getValue(), rendererAntialias.getValue(), rendererShadows.getValue(), rendererGammaInput.getValue(), rendererGammaOutput.getValue() );
-
-		}
-
-		function createRenderer( type, antialias, shadows, gammaIn, gammaOut ) {
-
-			if ( type === 'WebGLRenderer' && System.support.webgl === false ) {
-
-				type = 'CanvasRenderer';
-
-			}
-
-			rendererPropertiesRow.setDisplay( type === 'WebGLRenderer' ? '' : 'none' );
-
-			var renderer = new rendererTypes[ type ]( { antialias: antialias} );
-			renderer.gammaInput = gammaIn;
-			renderer.gammaOutput = gammaOut;
-			if ( shadows && renderer.shadowMap ) {
-
-				renderer.shadowMap.enabled = true;
-				// renderer.shadowMap.type = THREE.PCFSoftShadowMap;
-
-			}
-
-			signals.rendererChanged.dispatch( renderer );
-
-		}
-
-		createRenderer( config.getKey( 'project/renderer' ), config.getKey( 'project/renderer/antialias' ), config.getKey( 'project/renderer/shadows' ), config.getKey( 'project/renderer/gammaInput' ), config.getKey( 'project/renderer/gammaOutput' ) );
-
-		return container;
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Sidebar.Project', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Sidebar.Properties = function ( editor ) {
-
-		var signals = editor.signals;
-
-		var container = new UI.Span();
-
-		var objectTab = new UI.Text( 'OBJECT' ).onClick( onClick );
-		var geometryTab = new UI.Text( 'GEOMETRY' ).onClick( onClick );
-		var materialTab = new UI.Text( 'MATERIAL' ).onClick( onClick );
-
-		var tabs = new UI.Div();
-		tabs.setId( 'tabs' );
-		tabs.add( objectTab, geometryTab, materialTab );
-		container.add( tabs );
-
-		function onClick( event ) {
-
-			select( event.target.textContent );
-
-		}
-
-		//
-
-		var object = new UI.Span().add(
-			new Sidebar.Object( editor )
-		);
-		container.add( object );
-
-		var geometry = new UI.Span().add(
-			new Sidebar.Geometry( editor )
-		);
-		container.add( geometry );
-
-		var material = new UI.Span().add(
-			new Sidebar.Material( editor )
-		);
-		container.add( material );
-
-		//
-
-		function select( section ) {
-
-			objectTab.setClass( '' );
-			geometryTab.setClass( '' );
-			materialTab.setClass( '' );
-
-			object.setDisplay( 'none' );
-			geometry.setDisplay( 'none' );
-			material.setDisplay( 'none' );
-
-			switch ( section ) {
-				case 'OBJECT':
-					objectTab.setClass( 'selected' );
-					object.setDisplay( '' );
-					break;
-				case 'GEOMETRY':
-					geometryTab.setClass( 'selected' );
-					geometry.setDisplay( '' );
-					break;
-				case 'MATERIAL':
-					materialTab.setClass( 'selected' );
-					material.setDisplay( '' );
-					break;
-			}
-
-		}
-
-		select( 'OBJECT' );
-
-		return container;
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Sidebar.Properties', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Sidebar.Scene = function ( editor ) {
-
-		var signals = editor.signals;
-
-		var container = new UI.Panel();
-		container.setBorderTop( '0' );
-		container.setPaddingTop( '20px' );
-
-		// outliner
-
-		function buildOption( object, draggable ) {
-
-			var option = document.createElement( 'div' );
-			option.draggable = draggable;
-			option.innerHTML = buildHTML( object );
-			option.value = object.id;
-
-			return option;
-
-		}
-
-		function getMaterialName( material ) {
-
-			if ( Array.isArray( material ) ) {
-
-				var array = [];
-
-				for ( var i = 0; i < material.length; i ++ ) {
-
-					array.push( material[ i ].name );
-
-				}
-
-				return array.join( ',' );
-
-			}
-
-			return material.name;
-
-		}
-
-		function buildHTML( object ) {
-
-			var html = '<span class="type ' + object.type + '"></span> ' + object.name;
-
-			if ( object instanceof THREE.Mesh ) {
-
-				var geometry = object.geometry;
-				var material = object.material;
-
-				html += ' <span class="type ' + geometry.type + '"></span> ' + geometry.name;
-				html += ' <span class="type ' + material.type + '"></span> ' + getMaterialName( material );
-
-			}
-
-			html += getScript( object.uuid );
-
-			return html;
-
-		}
-
-		function getScript( uuid ) {
-
-			if ( editor.scripts[ uuid ] !== undefined ) {
-
-				return ' <span class="type Script"></span>';
-
-			}
-
-			return '';
-
-		}
-
-		var ignoreObjectSelectedSignal = false;
-
-		var outliner = new UI.Outliner( editor );
-		outliner.setId( 'outliner' );
-		outliner.onChange( function () {
-
-			ignoreObjectSelectedSignal = true;
-
-			editor.selectById( parseInt( outliner.getValue() ) );
-
-			ignoreObjectSelectedSignal = false;
-
-		} );
-		outliner.onDblClick( function () {
-
-			editor.focusById( parseInt( outliner.getValue() ) );
-
-		} );
-		container.add( outliner );
-		container.add( new UI.Break() );
-
-		// background
-
-		function onBackgroundChanged() {
-
-			signals.sceneBackgroundChanged.dispatch( backgroundColor.getHexValue() );
-
-		}
-
-		var backgroundRow = new UI.Row();
-
-		var backgroundColor = new UI.Color().setValue( '#aaaaaa' ).onChange( onBackgroundChanged );
-
-		backgroundRow.add( new UI.Text( 'Background' ).setWidth( '90px' ) );
-		backgroundRow.add( backgroundColor );
-
-		container.add( backgroundRow );
-
-		// fog
-
-		function onFogChanged() {
-
-			signals.sceneFogChanged.dispatch(
-				fogType.getValue(),
-				fogColor.getHexValue(),
-				fogNear.getValue(),
-				fogFar.getValue(),
-				fogDensity.getValue()
-			);
-
-		}
-
-		var fogTypeRow = new UI.Row();
-		var fogType = new UI.Select().setOptions( {
-
-			'None': 'None',
-			'Fog': 'Linear',
-			'FogExp2': 'Exponential'
-
-		} ).setWidth( '150px' );
-		fogType.onChange( function () {
-
-			onFogChanged();
-			refreshFogUI();
-
-		} );
-
-		fogTypeRow.add( new UI.Text( 'Fog' ).setWidth( '90px' ) );
-		fogTypeRow.add( fogType );
-
-		container.add( fogTypeRow );
-
-		// fog color
-
-		var fogPropertiesRow = new UI.Row();
-		fogPropertiesRow.setDisplay( 'none' );
-		fogPropertiesRow.setMarginLeft( '90px' );
-		container.add( fogPropertiesRow );
-
-		var fogColor = new UI.Color().setValue( '#aaaaaa' );
-		fogColor.onChange( onFogChanged );
-		fogPropertiesRow.add( fogColor );
-
-		// fog near
-
-		var fogNear = new UI.Number( 0.1 ).setWidth( '40px' ).setRange( 0, Infinity ).onChange( onFogChanged );
-		fogPropertiesRow.add( fogNear );
-
-		// fog far
-
-		var fogFar = new UI.Number( 50 ).setWidth( '40px' ).setRange( 0, Infinity ).onChange( onFogChanged );
-		fogPropertiesRow.add( fogFar );
-
-		// fog density
-
-		var fogDensity = new UI.Number( 0.05 ).setWidth( '40px' ).setRange( 0, 0.1 ).setPrecision( 3 ).onChange( onFogChanged );
-		fogPropertiesRow.add( fogDensity );
-
-		//
-
-		function refreshUI() {
-
-			var camera = editor.camera;
-			var scene = editor.scene;
-
-			var options = [];
-
-			options.push( buildOption( camera, false ) );
-			options.push( buildOption( scene, false ) );
-
-			( function addObjects( objects, pad ) {
-
-				for ( var i = 0, l = objects.length; i < l; i ++ ) {
-
-					var object = objects[ i ];
-
-					var option = buildOption( object, true );
-					option.style.paddingLeft = ( pad * 10 ) + 'px';
-					options.push( option );
-
-					addObjects( object.children, pad + 1 );
-
-				}
-
-			} )( scene.children, 1 );
-
-			outliner.setOptions( options );
-
-			if ( editor.selected !== null ) {
-
-				outliner.setValue( editor.selected.id );
-
-			}
-
-			if ( scene.background ) {
-
-				backgroundColor.setHexValue( scene.background.getHex() );
-
-			}
-
-			if ( scene.fog ) {
-
-				fogColor.setHexValue( scene.fog.color.getHex() );
-
-				if ( scene.fog instanceof THREE.Fog ) {
-
-					fogType.setValue( "Fog" );
-					fogNear.setValue( scene.fog.near );
-					fogFar.setValue( scene.fog.far );
-
-				} else if ( scene.fog instanceof THREE.FogExp2 ) {
-
-					fogType.setValue( "FogExp2" );
-					fogDensity.setValue( scene.fog.density );
-
-				}
-
-			} else {
-
-				fogType.setValue( "None" );
-
-			}
-
-			refreshFogUI();
-
-		}
-
-		function refreshFogUI() {
-
-			var type = fogType.getValue();
-
-			fogPropertiesRow.setDisplay( type === 'None' ? 'none' : '' );
-			fogNear.setDisplay( type === 'Fog' ? '' : 'none' );
-			fogFar.setDisplay( type === 'Fog' ? '' : 'none' );
-			fogDensity.setDisplay( type === 'FogExp2' ? '' : 'none' );
-
-		}
-
-		refreshUI();
-
-		// events
-
-		signals.editorCleared.add( refreshUI );
-
-		signals.sceneGraphChanged.add( refreshUI );
-
-		signals.objectChanged.add( function ( object ) {
-
-			var options = outliner.options;
-
-			for ( var i = 0; i < options.length; i ++ ) {
-
-				var option = options[ i ];
-
-				if ( option.value === object.id ) {
-
-					option.innerHTML = buildHTML( object );
-					return;
-
-				}
-
-			}
-
-		} );
-
-		signals.objectSelected.add( function ( object ) {
-
-			if ( ignoreObjectSelectedSignal === true ) return;
-
-			outliner.setValue( object !== null ? object.id : null );
-
-		} );
-
-		return container;
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Sidebar.Scene', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Sidebar.Script = function ( editor ) {
-
-		var signals = editor.signals;
-
-		var container = new UI.Panel();
-		container.setDisplay( 'none' );
-
-		container.add( new UI.Text( 'Script' ).setTextTransform( 'uppercase' ) );
-		container.add( new UI.Break() );
-		container.add( new UI.Break() );
-
-		//
-
-		var scriptsContainer = new UI.Row();
-		container.add( scriptsContainer );
-
-		var newScript = new UI.Button( 'New' );
-		newScript.onClick( function () {
-
-			var script = { name: '', source: 'function update( event ) {}' };
-			editor.execute( new AddScriptCommand( editor.selected, script ) );
-
-		} );
-		container.add( newScript );
-
-		/*
-		var loadScript = new UI.Button( 'Load' );
-		loadScript.setMarginLeft( '4px' );
-		container.add( loadScript );
-		*/
-
-		//
-
-		function update() {
-
-			scriptsContainer.clear();
-			scriptsContainer.setDisplay( 'none' );
-
-			var object = editor.selected;
-
-			if ( object === null ) {
-
-				return;
-
-			}
-
-			var scripts = editor.scripts[ object.uuid ];
-
-			if ( scripts !== undefined ) {
-
-				scriptsContainer.setDisplay( 'block' );
-
-				for ( var i = 0; i < scripts.length; i ++ ) {
-
-					( function ( object, script ) {
-
-						var name = new UI.Input( script.name ).setWidth( '130px' ).setFontSize( '12px' );
-						name.onChange( function () {
-
-							editor.execute( new SetScriptValueCommand( editor.selected, script, 'name', this.getValue() ) );
-
-						} );
-						scriptsContainer.add( name );
-
-						var edit = new UI.Button( 'Edit' );
-						edit.setMarginLeft( '4px' );
-						edit.onClick( function () {
-
-							signals.editScript.dispatch( object, script );
-
-						} );
-						scriptsContainer.add( edit );
-
-						var remove = new UI.Button( 'Remove' );
-						remove.setMarginLeft( '4px' );
-						remove.onClick( function () {
-
-							if ( confirm( 'Are you sure?' ) ) {
-
-								editor.execute( new RemoveScriptCommand( editor.selected, script ) );
-
-							}
-
-						} );
-						scriptsContainer.add( remove );
-
-						scriptsContainer.add( new UI.Break() );
-
-					} )( object, scripts[ i ] );
-
-				}
-
-			}
-
-		}
-
-		// signals
-
-		signals.objectSelected.add( function ( object ) {
-
-			if ( object !== null && editor.camera !== object ) {
-
-				container.setDisplay( 'block' );
-
-				update();
-
-			} else {
-
-				container.setDisplay( 'none' );
-
-			}
-
-		} );
-
-		signals.scriptAdded.add( update );
-		signals.scriptRemoved.add( update );
-		signals.scriptChanged.add( update );
-
-		return container;
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Sidebar.Script', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Sidebar.Settings = function ( editor ) {
-
-		var config = editor.config;
-		var signals = editor.signals;
-
-		var container = new UI.Panel();
-		container.setBorderTop( '0' );
-		container.setPaddingTop( '20px' );
-
-		// class
-
-		var options = {
-			'css/light.css': 'light',
-			'css/dark.css': 'dark'
-		};
-
-		var themeRow = new UI.Row();
-		var theme = new UI.Select().setWidth( '150px' );
-		theme.setOptions( options );
-
-		if ( config.getKey( 'theme' ) !== undefined ) {
-
-			theme.setValue( config.getKey( 'theme' ) );
-
-		}
-
-		theme.onChange( function () {
-
-			var value = this.getValue();
-
-			editor.setTheme( value );
-			editor.config.setKey( 'theme', value );
-
-		} );
-
-		themeRow.add( new UI.Text( 'Theme' ).setWidth( '90px' ) );
-		themeRow.add( theme );
-
-		container.add( themeRow );
-
-		return container;
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Sidebar.Settings', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Storage', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Toolbar', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Viewport', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author mrdoob / http://mrdoob.com/
-	 */
-
-	Viewport.Info = function ( editor ) {
-
-		var signals = editor.signals;
-
-		var container = new UI.Panel();
-		container.setId( 'info' );
-		container.setPosition( 'absolute' );
-		container.setLeft( '10px' );
-		container.setBottom( '10px' );
-		container.setFontSize( '12px' );
-		container.setColor( '#fff' );
-
-		var objectsText = new UI.Text( '0' ).setMarginLeft( '6px' );
-		var verticesText = new UI.Text( '0' ).setMarginLeft( '6px' );
-		var trianglesText = new UI.Text( '0' ).setMarginLeft( '6px' );
-
-		container.add( new UI.Text( 'objects' ), objectsText, new UI.Break() );
-		container.add( new UI.Text( 'vertices' ), verticesText, new UI.Break() );
-		container.add( new UI.Text( 'triangles' ), trianglesText, new UI.Break() );
-
-		signals.objectAdded.add( update );
-		signals.objectRemoved.add( update );
-		signals.geometryChanged.add( update );
-
-		//
-
-		function update() {
-
-			var scene = editor.scene;
-
-			var objects = 0, vertices = 0, triangles = 0;
-
-			for ( var i = 0, l = scene.children.length; i < l; i ++ ) {
-
-				var object = scene.children[ i ];
-
-				object.traverseVisible( function ( object ) {
-
-					objects ++;
-
-					if ( object instanceof THREE.Mesh ) {
-
-						var geometry = object.geometry;
-
-						if ( geometry instanceof THREE.Geometry ) {
-
-							vertices += geometry.vertices.length;
-							triangles += geometry.faces.length;
-
-						} else if ( geometry instanceof THREE.BufferGeometry ) {
-
-							if ( geometry.index !== null ) {
-
-								vertices += geometry.index.count * 3;
-								triangles += geometry.index.count;
-
-							} else {
-
-								vertices += geometry.attributes.position.count;
-								triangles += geometry.attributes.position.count / 3;
-
-							}
-
-						}
-
-					}
-
-				} );
-
-			}
-
-			objectsText.setValue( objects.format() );
-			verticesText.setValue( vertices.format() );
-			trianglesText.setValue( triangles.format() );
-
-		}
-
-		return container;
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module.todo( 'Viewport.Info', () => {
-
-			QUnit.test( 'write me !', ( assert ) => {
-
-				assert.ok( false, "everything's gonna be alright" );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author dforrer / https://github.com/dforrer
-	 * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
-	 */
-
-	/**
-	 * @param object THREE.Object3D
-	 * @constructor
-	 */
-
-	var AddObjectCommand$1 = function ( object ) {
-
-		Command.call( this );
-
-		this.type = 'AddObjectCommand';
-
-		this.object = object;
-		if ( object !== undefined ) {
-
-			this.name = 'Add Object: ' + object.name;
-
-		}
-
-	};
-
-	AddObjectCommand$1.prototype = {
-
-		execute: function () {
-
-			this.editor.addObject( this.object );
-			this.editor.select( this.object );
-
-		},
-
-		undo: function () {
-
-			this.editor.removeObject( this.object );
-			this.editor.deselect();
-
-		},
-
-		toJSON: function () {
-
-			var output = Command.prototype.toJSON.call( this );
-			output.object = this.object.toJSON();
-
-			return output;
-
-		},
-
-		fromJSON: function ( json ) {
-
-			Command.prototype.fromJSON.call( this, json );
-
-			this.object = this.editor.objectByUuid( json.object.object.uuid );
-
-			if ( this.object === undefined ) {
-
-				var loader = new THREE.ObjectLoader();
-				this.object = loader.parse( json.object );
-
-			}
-
-		}
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module( 'Commands', () => {
-
-			QUnit.module.todo( 'AddObjectCommand', () => {
-
-				QUnit.test( 'write me !', ( assert ) => {
-
-					assert.ok( false, "everything's gonna be alright" );
-
-				} );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author dforrer / https://github.com/dforrer
-	 * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
-	 */
-
-	/**
-	 * @param object THREE.Object3D
-	 * @param script javascript object
-	 * @constructor
-	 */
-
-	var AddScriptCommand$1 = function ( object, script ) {
-
-		Command.call( this );
-
-		this.type = 'AddScriptCommand';
-		this.name = 'Add Script';
-
-		this.object = object;
-		this.script = script;
-
-	};
-
-	AddScriptCommand$1.prototype = {
-
-		execute: function () {
-
-			if ( this.editor.scripts[ this.object.uuid ] === undefined ) {
-
-				this.editor.scripts[ this.object.uuid ] = [];
-
-			}
-
-			this.editor.scripts[ this.object.uuid ].push( this.script );
-
-			this.editor.signals.scriptAdded.dispatch( this.script );
-
-		},
-
-		undo: function () {
-
-			if ( this.editor.scripts[ this.object.uuid ] === undefined ) return;
-
-			var index = this.editor.scripts[ this.object.uuid ].indexOf( this.script );
-
-			if ( index !== - 1 ) {
-
-				this.editor.scripts[ this.object.uuid ].splice( index, 1 );
-
-			}
-
-			this.editor.signals.scriptRemoved.dispatch( this.script );
-
-		},
-
-		toJSON: function () {
-
-			var output = Command.prototype.toJSON.call( this );
-
-			output.objectUuid = this.object.uuid;
-			output.script = this.script;
-
-			return output;
-
-		},
-
-		fromJSON: function ( json ) {
-
-			Command.prototype.fromJSON.call( this, json );
-
-			this.script = json.script;
-			this.object = this.editor.objectByUuid( json.objectUuid );
-
-		}
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module( 'Commands', () => {
-
-			QUnit.module.todo( 'AddScriptCommand', () => {
-
-				QUnit.test( 'write me !', ( assert ) => {
-
-					assert.ok( false, "everything's gonna be alright" );
-
-				} );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author dforrer / https://github.com/dforrer
-	 * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
-	 */
-
-	/**
-	 * @param object THREE.Object3D
-	 * @param newParent THREE.Object3D
-	 * @param newBefore THREE.Object3D
-	 * @constructor
-	 */
-
-	var MoveObjectCommand = function ( object, newParent, newBefore ) {
-
-		Command.call( this );
-
-		this.type = 'MoveObjectCommand';
-		this.name = 'Move Object';
-
-		this.object = object;
-		this.oldParent = ( object !== undefined ) ? object.parent : undefined;
-		this.oldIndex = ( this.oldParent !== undefined ) ? this.oldParent.children.indexOf( this.object ) : undefined;
-		this.newParent = newParent;
-
-		if ( newBefore !== undefined ) {
-
-			this.newIndex = ( newParent !== undefined ) ? newParent.children.indexOf( newBefore ) : undefined;
-
-		} else {
-
-			this.newIndex = ( newParent !== undefined ) ? newParent.children.length : undefined;
-
-		}
-
-		if ( this.oldParent === this.newParent && this.newIndex > this.oldIndex ) {
-
-			this.newIndex --;
-
-		}
-
-		this.newBefore = newBefore;
-
-	};
-
-	MoveObjectCommand.prototype = {
-
-		execute: function () {
-
-			this.oldParent.remove( this.object );
-
-			var children = this.newParent.children;
-			children.splice( this.newIndex, 0, this.object );
-			this.object.parent = this.newParent;
-
-			this.editor.signals.sceneGraphChanged.dispatch();
-
-		},
-
-		undo: function () {
-
-			this.newParent.remove( this.object );
-
-			var children = this.oldParent.children;
-			children.splice( this.oldIndex, 0, this.object );
-			this.object.parent = this.oldParent;
-
-			this.editor.signals.sceneGraphChanged.dispatch();
-
-		},
-
-		toJSON: function () {
-
-			var output = Command.prototype.toJSON.call( this );
-
-			output.objectUuid = this.object.uuid;
-			output.newParentUuid = this.newParent.uuid;
-			output.oldParentUuid = this.oldParent.uuid;
-			output.newIndex = this.newIndex;
-			output.oldIndex = this.oldIndex;
-
-			return output;
-
-		},
-
-		fromJSON: function ( json ) {
-
-			Command.prototype.fromJSON.call( this, json );
-
-			this.object = this.editor.objectByUuid( json.objectUuid );
-			this.oldParent = this.editor.objectByUuid( json.oldParentUuid );
-			if ( this.oldParent === undefined ) {
-
-				this.oldParent = this.editor.scene;
-
-			}
-			this.newParent = this.editor.objectByUuid( json.newParentUuid );
-			if ( this.newParent === undefined ) {
-
-				this.newParent = this.editor.scene;
-
-			}
-			this.newIndex = json.newIndex;
-			this.oldIndex = json.oldIndex;
-
-		}
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module( 'Commands', () => {
-
-			QUnit.module.todo( 'MoveObjectCommand', () => {
-
-				QUnit.test( 'write me !', ( assert ) => {
-
-					assert.ok( false, "everything's gonna be alright" );
-
-				} );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author dforrer / https://github.com/dforrer
-	 * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
-	 */
-
-	/**
-	 * @param cmdArray array containing command objects
-	 * @constructor
-	 */
-
-	var MultiCmdsCommand$1 = function ( cmdArray ) {
-
-		Command.call( this );
-
-		this.type = 'MultiCmdsCommand';
-		this.name = 'Multiple Changes';
-
-		this.cmdArray = ( cmdArray !== undefined ) ? cmdArray : [];
-
-	};
-
-	MultiCmdsCommand$1.prototype = {
-
-		execute: function () {
-
-			this.editor.signals.sceneGraphChanged.active = false;
-
-			for ( var i = 0; i < this.cmdArray.length; i ++ ) {
-
-				this.cmdArray[ i ].execute();
-
-			}
-
-			this.editor.signals.sceneGraphChanged.active = true;
-			this.editor.signals.sceneGraphChanged.dispatch();
-
-		},
-
-		undo: function () {
-
-			this.editor.signals.sceneGraphChanged.active = false;
-
-			for ( var i = this.cmdArray.length - 1; i >= 0; i -- ) {
-
-				this.cmdArray[ i ].undo();
-
-			}
-
-			this.editor.signals.sceneGraphChanged.active = true;
-			this.editor.signals.sceneGraphChanged.dispatch();
-
-		},
-
-		toJSON: function () {
-
-			var output = Command.prototype.toJSON.call( this );
-
-			var cmds = [];
-			for ( var i = 0; i < this.cmdArray.length; i ++ ) {
-
-				cmds.push( this.cmdArray[ i ].toJSON() );
-
-			}
-			output.cmds = cmds;
-
-			return output;
-
-		},
-
-		fromJSON: function ( json ) {
-
-			Command.prototype.fromJSON.call( this, json );
-
-			var cmds = json.cmds;
-			for ( var i = 0; i < cmds.length; i ++ ) {
-
-				var cmd = new window[ cmds[ i ].type ]();	// creates a new object of type "json.type"
-				cmd.fromJSON( cmds[ i ] );
-				this.cmdArray.push( cmd );
-
-			}
-
-		}
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module( 'Commands', () => {
-
-			QUnit.module.todo( 'MultiCmdsCommand', () => {
-
-				QUnit.test( 'write me !', ( assert ) => {
-
-					assert.ok( false, "everything's gonna be alright" );
-
-				} );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author dforrer / https://github.com/dforrer
-	 * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
-	 */
-
-	/**
-	 * @param object THREE.Object3D
-	 * @constructor
-	 */
-
-	var RemoveObjectCommand$1 = function ( object ) {
-
-		Command.call( this );
-
-		this.type = 'RemoveObjectCommand';
-		this.name = 'Remove Object';
-
-		this.object = object;
-		this.parent = ( object !== undefined ) ? object.parent : undefined;
-		if ( this.parent !== undefined ) {
-
-			this.index = this.parent.children.indexOf( this.object );
-
-		}
-
-	};
-
-	RemoveObjectCommand$1.prototype = {
-
-		execute: function () {
-
-			var scope = this.editor;
-			this.object.traverse( function ( child ) {
-
-				scope.removeHelper( child );
-
-			} );
-
-			this.parent.remove( this.object );
-			this.editor.select( this.parent );
-
-			this.editor.signals.objectRemoved.dispatch( this.object );
-			this.editor.signals.sceneGraphChanged.dispatch();
-
-		},
-
-		undo: function () {
-
-			var scope = this.editor;
-
-			this.object.traverse( function ( child ) {
-
-				if ( child.geometry !== undefined ) scope.addGeometry( child.geometry );
-				if ( child.material !== undefined ) scope.addMaterial( child.material );
-
-				scope.addHelper( child );
-
-			} );
-
-			this.parent.children.splice( this.index, 0, this.object );
-			this.object.parent = this.parent;
-			this.editor.select( this.object );
-
-			this.editor.signals.objectAdded.dispatch( this.object );
-			this.editor.signals.sceneGraphChanged.dispatch();
-
-		},
-
-		toJSON: function () {
-
-			var output = Command.prototype.toJSON.call( this );
-			output.object = this.object.toJSON();
-			output.index = this.index;
-			output.parentUuid = this.parent.uuid;
-
-			return output;
-
-		},
-
-		fromJSON: function ( json ) {
-
-			Command.prototype.fromJSON.call( this, json );
-
-			this.parent = this.editor.objectByUuid( json.parentUuid );
-			if ( this.parent === undefined ) {
-
-				this.parent = this.editor.scene;
-
-			}
-
-			this.index = json.index;
-
-			this.object = this.editor.objectByUuid( json.object.object.uuid );
-			if ( this.object === undefined ) {
-
-				var loader = new THREE.ObjectLoader();
-				this.object = loader.parse( json.object );
-
-			}
-
-		}
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module( 'Commands', () => {
-
-			QUnit.module.todo( 'RemoveObjectCommand', () => {
-
-				QUnit.test( 'write me !', ( assert ) => {
-
-					assert.ok( false, "everything's gonna be alright" );
-
-				} );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author dforrer / https://github.com/dforrer
-	 * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
-	 */
-
-	/**
-	 * @param object THREE.Object3D
-	 * @param script javascript object
-	 * @constructor
-	 */
-
-	var RemoveScriptCommand$1 = function ( object, script ) {
-
-		Command.call( this );
-
-		this.type = 'RemoveScriptCommand';
-		this.name = 'Remove Script';
-
-		this.object = object;
-		this.script = script;
-		if ( this.object && this.script ) {
-
-			this.index = this.editor.scripts[ this.object.uuid ].indexOf( this.script );
-
-		}
-
-	};
-
-	RemoveScriptCommand$1.prototype = {
-
-		execute: function () {
-
-			if ( this.editor.scripts[ this.object.uuid ] === undefined ) return;
-
-			if ( this.index !== - 1 ) {
-
-				this.editor.scripts[ this.object.uuid ].splice( this.index, 1 );
-
-			}
-
-			this.editor.signals.scriptRemoved.dispatch( this.script );
-
-		},
-
-		undo: function () {
-
-			if ( this.editor.scripts[ this.object.uuid ] === undefined ) {
-
-				this.editor.scripts[ this.object.uuid ] = [];
-
-			}
-
-			this.editor.scripts[ this.object.uuid ].splice( this.index, 0, this.script );
-
-			this.editor.signals.scriptAdded.dispatch( this.script );
-
-		},
-
-		toJSON: function () {
-
-			var output = Command.prototype.toJSON.call( this );
-
-			output.objectUuid = this.object.uuid;
-			output.script = this.script;
-			output.index = this.index;
-
-			return output;
-
-		},
-
-		fromJSON: function ( json ) {
-
-			Command.prototype.fromJSON.call( this, json );
-
-			this.script = json.script;
-			this.index = json.index;
-			this.object = this.editor.objectByUuid( json.objectUuid );
-
-		}
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module( 'Commands', () => {
-
-			QUnit.module.todo( 'RemoveScriptCommand', () => {
-
-				QUnit.test( 'write me !', ( assert ) => {
-
-					assert.ok( false, "everything's gonna be alright" );
-
-				} );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author dforrer / https://github.com/dforrer
-	 * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
-	 */
-
-	/**
-	 * @param object THREE.Object3D
-	 * @param attributeName string
-	 * @param newValue integer representing a hex color value
-	 * @constructor
-	 */
-
-	var SetColorCommand$1 = function ( object, attributeName, newValue ) {
-
-		Command.call( this );
-
-		this.type = 'SetColorCommand';
-		this.name = 'Set ' + attributeName;
-		this.updatable = true;
-
-		this.object = object;
-		this.attributeName = attributeName;
-		this.oldValue = ( object !== undefined ) ? this.object[ this.attributeName ].getHex() : undefined;
-		this.newValue = newValue;
-
-	};
-
-	SetColorCommand$1.prototype = {
-
-		execute: function () {
-
-			this.object[ this.attributeName ].setHex( this.newValue );
-			this.editor.signals.objectChanged.dispatch( this.object );
-
-		},
-
-		undo: function () {
-
-			this.object[ this.attributeName ].setHex( this.oldValue );
-			this.editor.signals.objectChanged.dispatch( this.object );
-
-		},
-
-		update: function ( cmd ) {
-
-			this.newValue = cmd.newValue;
-
-		},
-
-		toJSON: function () {
-
-			var output = Command.prototype.toJSON.call( this );
-
-			output.objectUuid = this.object.uuid;
-			output.attributeName = this.attributeName;
-			output.oldValue = this.oldValue;
-			output.newValue = this.newValue;
-
-			return output;
-
-		},
-
-		fromJSON: function ( json ) {
-
-			Command.prototype.fromJSON.call( this, json );
-
-			this.object = this.editor.objectByUuid( json.objectUuid );
-			this.attributeName = json.attributeName;
-			this.oldValue = json.oldValue;
-			this.newValue = json.newValue;
-
-		}
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module( 'Commands', () => {
-
-			QUnit.module.todo( 'SetColorCommand', () => {
-
-				QUnit.test( 'write me !', ( assert ) => {
-
-					assert.ok( false, "everything's gonna be alright" );
-
-				} );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author dforrer / https://github.com/dforrer
-	 * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
-	 */
-
-	/**
-	 * @param object THREE.Object3D
-	 * @param newGeometry THREE.Geometry
-	 * @constructor
-	 */
-
-	var SetGeometryCommand$1 = function ( object, newGeometry ) {
-
-		Command.call( this );
-
-		this.type = 'SetGeometryCommand';
-		this.name = 'Set Geometry';
-		this.updatable = true;
-
-		this.object = object;
-		this.oldGeometry = ( object !== undefined ) ? object.geometry : undefined;
-		this.newGeometry = newGeometry;
-
-	};
-
-	SetGeometryCommand$1.prototype = {
-
-		execute: function () {
-
-			this.object.geometry.dispose();
-			this.object.geometry = this.newGeometry;
-			this.object.geometry.computeBoundingSphere();
-
-			this.editor.signals.geometryChanged.dispatch( this.object );
-			this.editor.signals.sceneGraphChanged.dispatch();
-
-		},
-
-		undo: function () {
-
-			this.object.geometry.dispose();
-			this.object.geometry = this.oldGeometry;
-			this.object.geometry.computeBoundingSphere();
-
-			this.editor.signals.geometryChanged.dispatch( this.object );
-			this.editor.signals.sceneGraphChanged.dispatch();
-
-		},
-
-		update: function ( cmd ) {
-
-			this.newGeometry = cmd.newGeometry;
-
-		},
-
-		toJSON: function () {
-
-			var output = Command.prototype.toJSON.call( this );
-
-			output.objectUuid = this.object.uuid;
-			output.oldGeometry = this.object.geometry.toJSON();
-			output.newGeometry = this.newGeometry.toJSON();
-
-			return output;
-
-		},
-
-		fromJSON: function ( json ) {
-
-			Command.prototype.fromJSON.call( this, json );
-
-			this.object = this.editor.objectByUuid( json.objectUuid );
-
-			this.oldGeometry = parseGeometry( json.oldGeometry );
-			this.newGeometry = parseGeometry( json.newGeometry );
-
-			function parseGeometry ( data ) {
-
-				var loader = new THREE.ObjectLoader();
-				return loader.parseGeometries( [ data ] )[ data.uuid ];
-
-			}
-
-		}
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module( 'Commands', () => {
-
-			QUnit.module.todo( 'SetGeometryCommand', () => {
-
-				QUnit.test( 'write me !', ( assert ) => {
-
-					assert.ok( false, "everything's gonna be alright" );
-
-				} );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author dforrer / https://github.com/dforrer
-	 * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
-	 */
-
-	/**
-	 * @param object THREE.Object3D
-	 * @param attributeName string
-	 * @param newValue number, string, boolean or object
-	 * @constructor
-	 */
-
-	var SetGeometryValueCommand$1 = function ( object, attributeName, newValue ) {
-
-		Command.call( this );
-
-		this.type = 'SetGeometryValueCommand';
-		this.name = 'Set Geometry.' + attributeName;
-
-		this.object = object;
-		this.attributeName = attributeName;
-		this.oldValue = ( object !== undefined ) ? object.geometry[ attributeName ] : undefined;
-		this.newValue = newValue;
-
-	};
-
-	SetGeometryValueCommand$1.prototype = {
-
-		execute: function () {
-
-			this.object.geometry[ this.attributeName ] = this.newValue;
-			this.editor.signals.objectChanged.dispatch( this.object );
-			this.editor.signals.geometryChanged.dispatch();
-			this.editor.signals.sceneGraphChanged.dispatch();
-
-		},
-
-		undo: function () {
-
-			this.object.geometry[ this.attributeName ] = this.oldValue;
-			this.editor.signals.objectChanged.dispatch( this.object );
-			this.editor.signals.geometryChanged.dispatch();
-			this.editor.signals.sceneGraphChanged.dispatch();
-
-		},
-
-		toJSON: function () {
-
-			var output = Command.prototype.toJSON.call( this );
-
-			output.objectUuid = this.object.uuid;
-			output.attributeName = this.attributeName;
-			output.oldValue = this.oldValue;
-			output.newValue = this.newValue;
-
-			return output;
-
-		},
-
-		fromJSON: function ( json ) {
-
-			Command.prototype.fromJSON.call( this, json );
-
-			this.object = this.editor.objectByUuid( json.objectUuid );
-			this.attributeName = json.attributeName;
-			this.oldValue = json.oldValue;
-			this.newValue = json.newValue;
-
-		}
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module( 'Commands', () => {
-
-			QUnit.module.todo( 'SetGeometryValueCommand', () => {
-
-				QUnit.test( 'write me !', ( assert ) => {
-
-					assert.ok( false, "everything's gonna be alright" );
-
-				} );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author dforrer / https://github.com/dforrer
-	 * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
-	 */
-
-	/**
-	 * @param object THREE.Object3D
-	 * @param attributeName string
-	 * @param newValue integer representing a hex color value
-	 * @constructor
-	 */
-
-	var SetMaterialColorCommand$1 = function ( object, attributeName, newValue, materialSlot ) {
-
-		Command.call( this );
-
-		this.type = 'SetMaterialColorCommand';
-		this.name = 'Set Material.' + attributeName;
-		this.updatable = true;
-
-		this.object = object;
-		this.material = this.editor.getObjectMaterial( object, materialSlot );
-
-		this.oldValue = ( this.material !== undefined ) ? this.material[ attributeName ].getHex() : undefined;
-		this.newValue = newValue;
-
-		this.attributeName = attributeName;
-
-	};
-
-	SetMaterialColorCommand$1.prototype = {
-
-		execute: function () {
-
-			this.material[ this.attributeName ].setHex( this.newValue );
-
-			this.editor.signals.materialChanged.dispatch( this.material );
-
-		},
-
-		undo: function () {
-
-			this.material[ this.attributeName ].setHex( this.oldValue );
-
-			this.editor.signals.materialChanged.dispatch( this.material );
-
-		},
-
-		update: function ( cmd ) {
-
-			this.newValue = cmd.newValue;
-
-		},
-
-		toJSON: function () {
-
-			var output = Command.prototype.toJSON.call( this );
-
-			output.objectUuid = this.object.uuid;
-			output.attributeName = this.attributeName;
-			output.oldValue = this.oldValue;
-			output.newValue = this.newValue;
-
-			return output;
-
-		},
-
-		fromJSON: function ( json ) {
-
-			Command.prototype.fromJSON.call( this, json );
-
-			this.object = this.editor.objectByUuid( json.objectUuid );
-			this.attributeName = json.attributeName;
-			this.oldValue = json.oldValue;
-			this.newValue = json.newValue;
-
-		}
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module( 'Commands', () => {
-
-			QUnit.module.todo( 'SetMaterialColorCommand', () => {
-
-				QUnit.test( 'write me !', ( assert ) => {
-
-					assert.ok( false, "everything's gonna be alright" );
-
-				} );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author dforrer / https://github.com/dforrer
-	 * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
-	 */
-
-	/**
-	 * @param object THREE.Object3D
-	 * @param newMaterial THREE.Material
-	 * @constructor
-	 */
-
-
-	var SetMaterialCommand$1 = function ( object, newMaterial, materialSlot ) {
-
-		Command.call( this );
-
-		this.type = 'SetMaterialCommand';
-		this.name = 'New Material';
-
-		this.object = object;
-		this.materialSlot = materialSlot;
-
-		this.oldMaterial = this.editor.getObjectMaterial( object, materialSlot );
-		this.newMaterial = newMaterial;
-
-	};
-
-	SetMaterialCommand$1.prototype = {
-
-		execute: function () {
-
-			this.editor.setObjectMaterial( this.object, this.materialSlot, this.newMaterial );
-			this.editor.signals.materialChanged.dispatch( this.newMaterial );
-
-		},
-
-		undo: function () {
-
-			this.editor.setObjectMaterial( this.object, this.materialSlot, this.oldMaterial );
-			this.editor.signals.materialChanged.dispatch( this.oldMaterial );
-
-		},
-
-		toJSON: function () {
-
-			var output = Command.prototype.toJSON.call( this );
-
-			output.objectUuid = this.object.uuid;
-			output.oldMaterial = this.oldMaterial.toJSON();
-			output.newMaterial = this.newMaterial.toJSON();
-
-			return output;
-
-		},
-
-		fromJSON: function ( json ) {
-
-			Command.prototype.fromJSON.call( this, json );
-
-			this.object = this.editor.objectByUuid( json.objectUuid );
-			this.oldMaterial = parseMaterial( json.oldMaterial );
-			this.newMaterial = parseMaterial( json.newMaterial );
-
-			function parseMaterial ( json ) {
-
-				var loader = new THREE.ObjectLoader();
-				var images = loader.parseImages( json.images );
-				var textures  = loader.parseTextures( json.textures, images );
-				var materials = loader.parseMaterials( [ json ], textures );
-				return materials[ json.uuid ];
-
-			}
-
-		}
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module( 'Commands', () => {
-
-			QUnit.module.todo( 'SetMaterialCommand', () => {
-
-				QUnit.test( 'write me !', ( assert ) => {
-
-					assert.ok( false, "everything's gonna be alright" );
-
-				} );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author dforrer / https://github.com/dforrer
-	 * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
-	 */
-
-	/**
-	 * @param object THREE.Object3D
-	 * @param mapName string
-	 * @param newMap THREE.Texture
-	 * @constructor
-	 */
-
-	var SetMaterialMapCommand$1 = function ( object, mapName, newMap, materialSlot ) {
-
-		Command.call( this );
-
-		this.type = 'SetMaterialMapCommand';
-		this.name = 'Set Material.' + mapName;
-
-		this.object = object;
-		this.material = this.editor.getObjectMaterial( object, materialSlot );
-
-		this.oldMap = ( object !== undefined ) ? this.material[ mapName ] : undefined;
-		this.newMap = newMap;
-
-		this.mapName = mapName;
-
-	};
-
-	SetMaterialMapCommand$1.prototype = {
-
-		execute: function () {
-
-			this.material[ this.mapName ] = this.newMap;
-			this.material.needsUpdate = true;
-
-			this.editor.signals.materialChanged.dispatch( this.material );
-
-		},
-
-		undo: function () {
-
-			this.material[ this.mapName ] = this.oldMap;
-			this.material.needsUpdate = true;
-
-			this.editor.signals.materialChanged.dispatch( this.material );
-
-		},
-
-		toJSON: function () {
-
-			var output = Command.prototype.toJSON.call( this );
-
-			output.objectUuid = this.object.uuid;
-			output.mapName = this.mapName;
-			output.newMap = serializeMap( this.newMap );
-			output.oldMap = serializeMap( this.oldMap );
-
-			return output;
-
-			// serializes a map (THREE.Texture)
-
-			function serializeMap ( map ) {
-
-				if ( map === null || map === undefined ) return null;
-
-				var meta = {
-					geometries: {},
-					materials: {},
-					textures: {},
-					images: {}
-				};
-
-				var json = map.toJSON( meta );
-				var images = extractFromCache( meta.images );
-				if ( images.length > 0 ) json.images = images;
-				json.sourceFile = map.sourceFile;
-
-				return json;
-
-			}
-
-			// Note: The function 'extractFromCache' is copied from Object3D.toJSON()
-
-			// extract data from the cache hash
-			// remove metadata on each item
-			// and return as array
-			function extractFromCache ( cache ) {
-
-				var values = [];
-				for ( var key in cache ) {
-
-					var data = cache[ key ];
-					delete data.metadata;
-					values.push( data );
-
-				}
-				return values;
-
-			}
-
-		},
-
-		fromJSON: function ( json ) {
-
-			Command.prototype.fromJSON.call( this, json );
-
-			this.object = this.editor.objectByUuid( json.objectUuid );
-			this.mapName = json.mapName;
-			this.oldMap = parseTexture( json.oldMap );
-			this.newMap = parseTexture( json.newMap );
-
-			function parseTexture ( json ) {
-
-				var map = null;
-				if ( json !== null ) {
-
-					var loader = new THREE.ObjectLoader();
-					var images = loader.parseImages( json.images );
-					var textures  = loader.parseTextures( [ json ], images );
-					map = textures[ json.uuid ];
-					map.sourceFile = json.sourceFile;
-
-				}
-				return map;
-
-			}
-
-		}
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module( 'Commands', () => {
-
-			QUnit.module.todo( 'SetMaterialMapCommand', () => {
-
-				QUnit.test( 'write me !', ( assert ) => {
-
-					assert.ok( false, "everything's gonna be alright" );
-
-				} );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author dforrer / https://github.com/dforrer
-	 * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
-	 */
-
-	/**
-	 * @param object THREE.Object3D
-	 * @param attributeName string
-	 * @param newValue number, string, boolean or object
-	 * @constructor
-	 */
-
-	var SetMaterialValueCommand$1 = function ( object, attributeName, newValue, materialSlot ) {
-
-		Command.call( this );
-
-		this.type = 'SetMaterialValueCommand';
-		this.name = 'Set Material.' + attributeName;
-		this.updatable = true;
-
-		this.object = object;
-		this.material = this.editor.getObjectMaterial( object, materialSlot );
-
-		this.oldValue = ( this.material !== undefined ) ? this.material[ attributeName ] : undefined;
-		this.newValue = newValue;
-
-		this.attributeName = attributeName;
-
-	};
-
-	SetMaterialValueCommand$1.prototype = {
-
-		execute: function () {
-
-			this.material[ this.attributeName ] = this.newValue;
-			this.material.needsUpdate = true;
-
-			this.editor.signals.objectChanged.dispatch( this.object );
-			this.editor.signals.materialChanged.dispatch( this.material );
-
-		},
-
-		undo: function () {
-
-			this.material[ this.attributeName ] = this.oldValue;
-			this.material.needsUpdate = true;
-
-			this.editor.signals.objectChanged.dispatch( this.object );
-			this.editor.signals.materialChanged.dispatch( this.material );
-
-		},
-
-		update: function ( cmd ) {
-
-			this.newValue = cmd.newValue;
-
-		},
-
-		toJSON: function () {
-
-			var output = Command.prototype.toJSON.call( this );
-
-			output.objectUuid = this.object.uuid;
-			output.attributeName = this.attributeName;
-			output.oldValue = this.oldValue;
-			output.newValue = this.newValue;
-
-			return output;
-
-		},
-
-		fromJSON: function ( json ) {
-
-			Command.prototype.fromJSON.call( this, json );
-
-			this.attributeName = json.attributeName;
-			this.oldValue = json.oldValue;
-			this.newValue = json.newValue;
-			this.object = this.editor.objectByUuid( json.objectUuid );
-
-		}
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module( 'Commands', () => {
-
-			QUnit.module.todo( 'SetMaterialValueCommand', () => {
-
-				QUnit.test( 'write me !', ( assert ) => {
-
-					assert.ok( false, "everything's gonna be alright" );
-
-				} );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author dforrer / https://github.com/dforrer
-	 * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
-	 */
-
-	/**
-	 * @param object THREE.Object3D
-	 * @param newPosition THREE.Vector3
-	 * @param optionalOldPosition THREE.Vector3
-	 * @constructor
-	 */
-
-	var SetPositionCommand$1 = function ( object, newPosition, optionalOldPosition ) {
-
-		Command.call( this );
-
-		this.type = 'SetPositionCommand';
-		this.name = 'Set Position';
-		this.updatable = true;
-
-		this.object = object;
-
-		if ( object !== undefined && newPosition !== undefined ) {
-
-			this.oldPosition = object.position.clone();
-			this.newPosition = newPosition.clone();
-
-		}
-
-		if ( optionalOldPosition !== undefined ) {
-
-			this.oldPosition = optionalOldPosition.clone();
-
-		}
-
-	};
-	SetPositionCommand$1.prototype = {
-
-		execute: function () {
-
-			this.object.position.copy( this.newPosition );
-			this.object.updateMatrixWorld( true );
-			this.editor.signals.objectChanged.dispatch( this.object );
-
-		},
-
-		undo: function () {
-
-			this.object.position.copy( this.oldPosition );
-			this.object.updateMatrixWorld( true );
-			this.editor.signals.objectChanged.dispatch( this.object );
-
-		},
-
-		update: function ( command ) {
-
-			this.newPosition.copy( command.newPosition );
-
-		},
-
-		toJSON: function () {
-
-			var output = Command.prototype.toJSON.call( this );
-
-			output.objectUuid = this.object.uuid;
-			output.oldPosition = this.oldPosition.toArray();
-			output.newPosition = this.newPosition.toArray();
-
-			return output;
-
-		},
-
-		fromJSON: function ( json ) {
-
-			Command.prototype.fromJSON.call( this, json );
-
-			this.object = this.editor.objectByUuid( json.objectUuid );
-			this.oldPosition = new THREE.Vector3().fromArray( json.oldPosition );
-			this.newPosition = new THREE.Vector3().fromArray( json.newPosition );
-
-		}
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module( 'Commands', () => {
-
-			QUnit.module.todo( 'SetPositionCommand', () => {
-
-				QUnit.test( 'write me !', ( assert ) => {
-
-					assert.ok( false, "everything's gonna be alright" );
-
-				} );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author dforrer / https://github.com/dforrer
-	 * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
-	 */
-
-	/**
-	 * @param object THREE.Object3D
-	 * @param newRotation THREE.Euler
-	 * @param optionalOldRotation THREE.Euler
-	 * @constructor
-	 */
-
-	var SetRotationCommand$1 = function ( object, newRotation, optionalOldRotation ) {
-
-		Command.call( this );
-
-		this.type = 'SetRotationCommand';
-		this.name = 'Set Rotation';
-		this.updatable = true;
-
-		this.object = object;
-
-		if ( object !== undefined && newRotation !== undefined ) {
-
-			this.oldRotation = object.rotation.clone();
-			this.newRotation = newRotation.clone();
-
-		}
-
-		if ( optionalOldRotation !== undefined ) {
-
-			this.oldRotation = optionalOldRotation.clone();
-
-		}
-
-	};
-
-	SetRotationCommand$1.prototype = {
-
-		execute: function () {
-
-			this.object.rotation.copy( this.newRotation );
-			this.object.updateMatrixWorld( true );
-			this.editor.signals.objectChanged.dispatch( this.object );
-
-		},
-
-		undo: function () {
-
-			this.object.rotation.copy( this.oldRotation );
-			this.object.updateMatrixWorld( true );
-			this.editor.signals.objectChanged.dispatch( this.object );
-
-		},
-
-		update: function ( command ) {
-
-			this.newRotation.copy( command.newRotation );
-
-		},
-
-		toJSON: function () {
-
-			var output = Command.prototype.toJSON.call( this );
-
-			output.objectUuid = this.object.uuid;
-			output.oldRotation = this.oldRotation.toArray();
-			output.newRotation = this.newRotation.toArray();
-
-			return output;
-
-		},
-
-		fromJSON: function ( json ) {
-
-			Command.prototype.fromJSON.call( this, json );
-
-			this.object = this.editor.objectByUuid( json.objectUuid );
-			this.oldRotation = new THREE.Euler().fromArray( json.oldRotation );
-			this.newRotation = new THREE.Euler().fromArray( json.newRotation );
-
-		}
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module( 'Commands', () => {
-
-			QUnit.module.todo( 'SetRotationCommand', () => {
-
-				QUnit.test( 'write me !', ( assert ) => {
-
-					assert.ok( false, "everything's gonna be alright" );
-
-				} );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author dforrer / https://github.com/dforrer
-	 * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
-	 */
-
-	/**
-	 * @param object THREE.Object3D
-	 * @param newScale THREE.Vector3
-	 * @param optionalOldScale THREE.Vector3
-	 * @constructor
-	 */
-
-	var SetScaleCommand$1 = function ( object, newScale, optionalOldScale ) {
-
-		Command.call( this );
-
-		this.type = 'SetScaleCommand';
-		this.name = 'Set Scale';
-		this.updatable = true;
-
-		this.object = object;
-
-		if ( object !== undefined && newScale !== undefined ) {
-
-			this.oldScale = object.scale.clone();
-			this.newScale = newScale.clone();
-
-		}
-
-		if ( optionalOldScale !== undefined ) {
-
-			this.oldScale = optionalOldScale.clone();
-
-		}
-
-	};
-
-	SetScaleCommand$1.prototype = {
-
-		execute: function () {
-
-			this.object.scale.copy( this.newScale );
-			this.object.updateMatrixWorld( true );
-			this.editor.signals.objectChanged.dispatch( this.object );
-
-		},
-
-		undo: function () {
-
-			this.object.scale.copy( this.oldScale );
-			this.object.updateMatrixWorld( true );
-			this.editor.signals.objectChanged.dispatch( this.object );
-
-		},
-
-		update: function ( command ) {
-
-			this.newScale.copy( command.newScale );
-
-		},
-
-		toJSON: function () {
-
-			var output = Command.prototype.toJSON.call( this );
-
-			output.objectUuid = this.object.uuid;
-			output.oldScale = this.oldScale.toArray();
-			output.newScale = this.newScale.toArray();
-
-			return output;
-
-		},
-
-		fromJSON: function ( json ) {
-
-			Command.prototype.fromJSON.call( this, json );
-
-			this.object = this.editor.objectByUuid( json.objectUuid );
-			this.oldScale = new THREE.Vector3().fromArray( json.oldScale );
-			this.newScale = new THREE.Vector3().fromArray( json.newScale );
-
-		}
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module( 'Commands', () => {
-
-			QUnit.module.todo( 'SetScaleCommand', () => {
-
-				QUnit.test( 'write me !', ( assert ) => {
-
-					assert.ok( false, "everything's gonna be alright" );
-
-				} );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author dforrer / https://github.com/dforrer
-	 * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
-	 */
-
-	/**
-	 * @param scene containing children to import
-	 * @constructor
-	 */
-
-	var SetSceneCommand$1 = function ( scene ) {
-
-		Command.call( this );
-
-		this.type = 'SetSceneCommand';
-		this.name = 'Set Scene';
-
-		this.cmdArray = [];
-
-		if ( scene !== undefined ) {
-
-			this.cmdArray.push( new SetUuidCommand( this.editor.scene, scene.uuid ) );
-			this.cmdArray.push( new SetValueCommand( this.editor.scene, 'name', scene.name ) );
-			this.cmdArray.push( new SetValueCommand( this.editor.scene, 'userData', JSON.parse( JSON.stringify( scene.userData ) ) ) );
-
-			while ( scene.children.length > 0 ) {
-
-				var child = scene.children.pop();
-				this.cmdArray.push( new AddObjectCommand( child ) );
-
-			}
-
-		}
-
-	};
-
-	SetSceneCommand$1.prototype = {
-
-		execute: function () {
-
-			this.editor.signals.sceneGraphChanged.active = false;
-
-			for ( var i = 0; i < this.cmdArray.length; i ++ ) {
-
-				this.cmdArray[ i ].execute();
-
-			}
-
-			this.editor.signals.sceneGraphChanged.active = true;
-			this.editor.signals.sceneGraphChanged.dispatch();
-
-		},
-
-		undo: function () {
-
-			this.editor.signals.sceneGraphChanged.active = false;
-
-			for ( var i = this.cmdArray.length - 1; i >= 0; i -- ) {
-
-				this.cmdArray[ i ].undo();
-
-			}
-
-			this.editor.signals.sceneGraphChanged.active = true;
-			this.editor.signals.sceneGraphChanged.dispatch();
-
-		},
-
-		toJSON: function () {
-
-			var output = Command.prototype.toJSON.call( this );
-
-			var cmds = [];
-			for ( var i = 0; i < this.cmdArray.length; i ++ ) {
-
-				cmds.push( this.cmdArray[ i ].toJSON() );
-
-			}
-			output.cmds = cmds;
-
-			return output;
-
-		},
-
-		fromJSON: function ( json ) {
-
-			Command.prototype.fromJSON.call( this, json );
-
-			var cmds = json.cmds;
-			for ( var i = 0; i < cmds.length; i ++ ) {
-
-				var cmd = new window[ cmds[ i ].type ]();	// creates a new object of type "json.type"
-				cmd.fromJSON( cmds[ i ] );
-				this.cmdArray.push( cmd );
-
-			}
-
-		}
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module( 'Commands', () => {
-
-			QUnit.module.todo( 'SetSceneCommand', () => {
-
-				QUnit.test( 'write me !', ( assert ) => {
-
-					assert.ok( false, "everything's gonna be alright" );
-
-				} );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author dforrer / https://github.com/dforrer
-	 * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
-	 */
-
-	/**
-	 * @param object THREE.Object3D
-	 * @param script javascript object
-	 * @param attributeName string
-	 * @param newValue string, object
-	 * @constructor
-	 */
-
-	var SetScriptValueCommand$1 = function ( object, script, attributeName, newValue ) {
-
-		Command.call( this );
-
-		this.type = 'SetScriptValueCommand';
-		this.name = 'Set Script.' + attributeName;
-		this.updatable = true;
-
-		this.object = object;
-		this.script = script;
-
-		this.attributeName = attributeName;
-		this.oldValue = ( script !== undefined ) ? script[ this.attributeName ] : undefined;
-		this.newValue = newValue;
-
-	};
-
-	SetScriptValueCommand$1.prototype = {
-
-		execute: function () {
-
-			this.script[ this.attributeName ] = this.newValue;
-
-			this.editor.signals.scriptChanged.dispatch();
-
-		},
-
-		undo: function () {
-
-			this.script[ this.attributeName ] = this.oldValue;
-
-			this.editor.signals.scriptChanged.dispatch();
-
-		},
-
-		update: function ( cmd ) {
-
-			this.newValue = cmd.newValue;
-
-		},
-
-		toJSON: function () {
-
-			var output = Command.prototype.toJSON.call( this );
-
-			output.objectUuid = this.object.uuid;
-			output.index = this.editor.scripts[ this.object.uuid ].indexOf( this.script );
-			output.attributeName = this.attributeName;
-			output.oldValue = this.oldValue;
-			output.newValue = this.newValue;
-
-			return output;
-
-		},
-
-		fromJSON: function ( json ) {
-
-			Command.prototype.fromJSON.call( this, json );
-
-			this.oldValue = json.oldValue;
-			this.newValue = json.newValue;
-			this.attributeName = json.attributeName;
-			this.object = this.editor.objectByUuid( json.objectUuid );
-			this.script = this.editor.scripts[ json.objectUuid ][ json.index ];
-
-		}
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module( 'Commands', () => {
-
-			QUnit.module.todo( 'SetScriptValueCommand', () => {
-
-				QUnit.test( 'write me !', ( assert ) => {
-
-					assert.ok( false, "everything's gonna be alright" );
-
-				} );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author dforrer / https://github.com/dforrer
-	 * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
-	 */
-
-	/**
-	 * @param object THREE.Object3D
-	 * @param newUuid string
-	 * @constructor
-	 */
-
-	var SetUuidCommand$1 = function ( object, newUuid ) {
-
-		Command.call( this );
-
-		this.type = 'SetUuidCommand';
-		this.name = 'Update UUID';
-
-		this.object = object;
-
-		this.oldUuid = ( object !== undefined ) ? object.uuid : undefined;
-		this.newUuid = newUuid;
-
-	};
-
-	SetUuidCommand$1.prototype = {
-
-		execute: function () {
-
-			this.object.uuid = this.newUuid;
-			this.editor.signals.objectChanged.dispatch( this.object );
-			this.editor.signals.sceneGraphChanged.dispatch();
-
-		},
-
-		undo: function () {
-
-			this.object.uuid = this.oldUuid;
-			this.editor.signals.objectChanged.dispatch( this.object );
-			this.editor.signals.sceneGraphChanged.dispatch();
-
-		},
-
-		toJSON: function () {
-
-			var output = Command.prototype.toJSON.call( this );
-
-			output.oldUuid = this.oldUuid;
-			output.newUuid = this.newUuid;
-
-			return output;
-
-		},
-
-		fromJSON: function ( json ) {
-
-			Command.prototype.fromJSON.call( this, json );
-
-			this.oldUuid = json.oldUuid;
-			this.newUuid = json.newUuid;
-			this.object = this.editor.objectByUuid( json.oldUuid );
-
-			if ( this.object === undefined ) {
-
-				this.object = this.editor.objectByUuid( json.newUuid );
-
-			}
-
-		}
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module( 'Commands', () => {
-
-			QUnit.module.todo( 'SetUuidCommand', () => {
-
-				QUnit.test( 'write me !', ( assert ) => {
-
-					assert.ok( false, "everything's gonna be alright" );
-
-				} );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author dforrer / https://github.com/dforrer
-	 * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
-	 */
-
-	/**
-	 * @param object THREE.Object3D
-	 * @param attributeName string
-	 * @param newValue number, string, boolean or object
-	 * @constructor
-	 */
-
-	var SetValueCommand$1 = function ( object, attributeName, newValue ) {
-
-		Command.call( this );
-
-		this.type = 'SetValueCommand';
-		this.name = 'Set ' + attributeName;
-		this.updatable = true;
-
-		this.object = object;
-		this.attributeName = attributeName;
-		this.oldValue = ( object !== undefined ) ? object[ attributeName ] : undefined;
-		this.newValue = newValue;
-
-	};
-
-	SetValueCommand$1.prototype = {
-
-		execute: function () {
-
-			this.object[ this.attributeName ] = this.newValue;
-			this.editor.signals.objectChanged.dispatch( this.object );
-			// this.editor.signals.sceneGraphChanged.dispatch();
-
-		},
-
-		undo: function () {
-
-			this.object[ this.attributeName ] = this.oldValue;
-			this.editor.signals.objectChanged.dispatch( this.object );
-			// this.editor.signals.sceneGraphChanged.dispatch();
-
-		},
-
-		update: function ( cmd ) {
-
-			this.newValue = cmd.newValue;
-
-		},
-
-		toJSON: function () {
-
-			var output = Command.prototype.toJSON.call( this );
-
-			output.objectUuid = this.object.uuid;
-			output.attributeName = this.attributeName;
-			output.oldValue = this.oldValue;
-			output.newValue = this.newValue;
-
-			return output;
-
-		},
-
-		fromJSON: function ( json ) {
-
-			Command.prototype.fromJSON.call( this, json );
-
-			this.attributeName = json.attributeName;
-			this.oldValue = json.oldValue;
-			this.newValue = json.newValue;
-			this.object = this.editor.objectByUuid( json.objectUuid );
-
-		}
-
-	};
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-	/* global QUnit */
-
-	QUnit.module( 'Editor', () => {
-
-		QUnit.module( 'Commands', () => {
-
-			QUnit.module.todo( 'SetValueCommand', () => {
-
-				QUnit.test( 'write me !', ( assert ) => {
-
-					assert.ok( false, "everything's gonna be alright" );
-
-				} );
-
-			} );
-
-		} );
-
-	} );
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-
-	// TODO (Itee) Editor is not es6 module so care to include order !!!
-	// TODO: all views could not be testable, waiting modular code before implement units tests on them
-
-	//editor
-	//editor/commands
-
-
-	//editor/others
-
-	} );
-
-})));

+ 0 - 15
test/unit/three.example.unit.js

@@ -1,15 +0,0 @@
-(function (global, factory) {
-	typeof exports === 'object' && typeof module !== 'undefined' ? factory() :
-	typeof define === 'function' && define.amd ? define(factory) :
-	(factory());
-}(this, (function () { 'use strict';
-
-	QUnit.module( "Example", () => {
-
-	/**
-	 * @author TristanVALCKE / https://github.com/Itee
-	 */
-
-	} );
-
-})));

File diff suppressed because it is too large
+ 0 - 35183
test/unit/three.source.unit.js


+ 1 - 1
utils/build/externs.js

@@ -3,5 +3,5 @@ var define;
 var module;
 var exports;
 var performance;
-var ImageBitmap;
+var ImageBitmap, createImageBitmap;
 var WebGL2RenderingContext;

Some files were not shown because too many files changed in this diff