Mr.doob 9 years ago
parent
commit
0593cf4bf9
100 changed files with 4242 additions and 2679 deletions
  1. 348 267
      build/three.js
  2. 440 392
      build/three.min.js
  3. 4 4
      docs/api/core/BufferAttribute.html
  4. 301 308
      docs/api/core/BufferGeometry.html
  5. 1 1
      docs/api/core/Face3.html
  6. 26 8
      docs/api/core/Geometry.html
  7. 1 1
      docs/api/core/Raycaster.html
  8. 2 2
      docs/api/examples/SpriteCanvasMaterial.html
  9. 0 115
      docs/api/extras/FontUtils.html
  10. 0 88
      docs/api/extras/ImageUtils.html
  11. 1 1
      docs/api/extras/SceneUtils.html
  12. 60 0
      docs/api/extras/curves/CatmullRomCurve3.html
  13. 2 2
      docs/api/extras/curves/ClosedSplineCurve3.html
  14. 0 20
      docs/api/extras/geometries/CubeGeometry.html
  15. 4 4
      docs/api/extras/geometries/LatheGeometry.html
  16. 1 3
      docs/api/extras/geometries/TextGeometry.html
  17. 2 1
      docs/api/lights/SpotLight.html
  18. 1 1
      docs/api/loaders/JSONLoader.html
  19. 1 1
      docs/api/loaders/MTLLoader.html
  20. 0 8
      docs/api/loaders/MaterialLoader.html
  21. 84 0
      docs/api/loaders/PCDLoader.html
  22. 0 5
      docs/api/loaders/XHRLoader.html
  23. 62 62
      docs/api/materials/LineBasicMaterial.html
  24. 16 1
      docs/api/materials/MeshLambertMaterial.html
  25. 24 9
      docs/api/materials/MeshPhongMaterial.html
  26. 2 2
      docs/api/materials/MultiMaterial.html
  27. 1 1
      docs/api/materials/ShaderMaterial.html
  28. 10 10
      docs/api/math/Box2.html
  29. 15 15
      docs/api/math/Box3.html
  30. 1 1
      docs/api/math/Euler.html
  31. 1 1
      docs/api/math/Frustum.html
  32. 3 3
      docs/api/math/Line3.html
  33. 1 1
      docs/api/math/Math.html
  34. 4 4
      docs/api/math/Matrix3.html
  35. 3 3
      docs/api/math/Matrix4.html
  36. 5 5
      docs/api/math/Plane.html
  37. 4 4
      docs/api/math/Quaternion.html
  38. 18 10
      docs/api/math/Ray.html
  39. 6 6
      docs/api/math/Sphere.html
  40. 5 5
      docs/api/math/Spline.html
  41. 3 3
      docs/api/math/Triangle.html
  42. 5 0
      docs/api/math/Vector2.html
  43. 2 2
      docs/api/objects/Sprite.html
  44. 10 10
      docs/api/renderers/WebGLRenderer.html
  45. 11 13
      docs/api/textures/CubeTexture.html
  46. 2 2
      docs/api/textures/Texture.html
  47. 10 12
      docs/list.js
  48. 14 14
      docs/page.js
  49. 34 33
      docs/scenes/bones-browser.html
  50. 2 11
      docs/scenes/geometry-browser.html
  51. 94 76
      docs/scenes/js/geometry.js
  52. 3 3
      docs/scenes/js/material.js
  53. 28 27
      docs/scenes/material-browser.html
  54. 49 28
      editor/css/dark.css
  55. 47 24
      editor/css/light.css
  56. 29 21
      editor/css/main.css
  57. 132 0
      editor/docs/Implementing additional commands for undo-redo.md
  58. 94 0
      editor/docs/Writing unit tests for undo-redo commands.md
  59. 55 14
      editor/index.html
  60. 47 0
      editor/js/Command.js
  61. 3 6
      editor/js/Config.js
  62. 77 21
      editor/js/Editor.js
  63. 277 35
      editor/js/History.js
  64. 148 119
      editor/js/Loader.js
  65. 93 91
      editor/js/Menubar.Add.js
  66. 63 18
      editor/js/Menubar.Edit.js
  67. 1 1
      editor/js/Menubar.Examples.js
  68. 65 71
      editor/js/Menubar.File.js
  69. 2 2
      editor/js/Menubar.Help.js
  70. 6 9
      editor/js/Menubar.Status.js
  71. 0 81
      editor/js/Menubar.View.js
  72. 0 1
      editor/js/Menubar.js
  73. 66 17
      editor/js/Script.js
  74. 2 2
      editor/js/Sidebar.Animation.js
  75. 12 16
      editor/js/Sidebar.Geometry.BoxGeometry.js
  76. 11 7
      editor/js/Sidebar.Geometry.BufferGeometry.js
  77. 10 12
      editor/js/Sidebar.Geometry.CircleGeometry.js
  78. 12 16
      editor/js/Sidebar.Geometry.CylinderGeometry.js
  79. 9 7
      editor/js/Sidebar.Geometry.Geometry.js
  80. 8 10
      editor/js/Sidebar.Geometry.IcosahedronGeometry.js
  81. 141 0
      editor/js/Sidebar.Geometry.LatheGeometry.js
  82. 5 3
      editor/js/Sidebar.Geometry.Modifiers.js
  83. 10 14
      editor/js/Sidebar.Geometry.PlaneGeometry.js
  84. 13 17
      editor/js/Sidebar.Geometry.SphereGeometry.js
  85. 8 8
      editor/js/Sidebar.Geometry.TeapotBufferGeometry.js
  86. 11 15
      editor/js/Sidebar.Geometry.TorusGeometry.js
  87. 13 17
      editor/js/Sidebar.Geometry.TorusKnotGeometry.js
  88. 37 38
      editor/js/Sidebar.Geometry.js
  89. 117 0
      editor/js/Sidebar.History.js
  90. 385 118
      editor/js/Sidebar.Material.js
  91. 126 95
      editor/js/Sidebar.Object.js
  92. 22 39
      editor/js/Sidebar.Project.js
  93. 76 0
      editor/js/Sidebar.Properties.js
  94. 25 17
      editor/js/Sidebar.Scene.js
  95. 5 6
      editor/js/Sidebar.Script.js
  96. 47 0
      editor/js/Sidebar.Settings.js
  97. 68 7
      editor/js/Sidebar.js
  98. 4 7
      editor/js/Toolbar.js
  99. 67 68
      editor/js/Viewport.js
  100. 66 0
      editor/js/commands/AddObjectCommand.js

File diff suppressed because it is too large
+ 348 - 267
build/three.js


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


+ 4 - 4
docs/api/core/BufferAttribute.html

@@ -15,17 +15,17 @@
 		</div>
 		</div>
 
 
 		<h2>Constructor</h2>
 		<h2>Constructor</h2>
-		<h3>[name]([page:Array array], [page:Integer itemSize])</h3>
+		<h3>[name]([page:TypedArray array], [page:Integer itemSize])</h3>
 		<div>
 		<div>
-		Instantiates this attribute with data from the associated buffer. The array can either be a regular Array or a Typed Array.
+		Instantiates this attribute with data from the associated buffer.
 		itemSize gives the number of values of the array that should be associated with a particular vertex.
 		itemSize gives the number of values of the array that should be associated with a particular vertex.
 		</div>
 		</div>
 
 
 		<h2>Properties</h2>
 		<h2>Properties</h2>
 
 
-		<h3>[property:Array array]</h3>
+		<h3>[property:TypedArray array]</h3>
 		<div>
 		<div>
-		Stores the data associated with this attribute; can be an Array or a Typed Array. This element should have <code>itemSize * numVertices</code> elements, where numVertices is the number of vertices in the associated [page:BufferGeometry geometry].
+		Stores the data associated with this attribute. This element should have <code>itemSize * numVertices</code> elements, where numVertices is the number of vertices in the associated [page:BufferGeometry geometry].
 		</div>
 		</div>
 
 
 		<h3>[property:Integer itemSize]</h3>
 		<h3>[property:Integer itemSize]</h3>

+ 301 - 308
docs/api/core/BufferGeometry.html

@@ -1,309 +1,302 @@
-<!DOCTYPE html>
-<html lang="en">
-	<head>
+<!DOCTYPE html>
+<html lang="en">
+	<head>
 		<meta charset="utf-8" />
 		<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">
-		<p>
-		This class is an efficient alternative to [page:Geometry], because it stores all data, including
-		vertex positions, face indices, normals, colors, UVs, and custom attributes within buffers; this
-		reduces the cost of passing all this data to the GPU.
-		This also makes BufferGeometry harder to work with than [page:Geometry]; rather than accessing
-		position data as [page:Vector3] objects, color data as [page:Color] objects, and so on, you have to
-		access the raw data from the appropriate [page:BufferAttribute attribute] buffer. This makes
-		BufferGeometry best-suited for static objects where you don't need to manipulate the geometry much
-		after instantiating it.
-		</p>
-
-		<h3>Example</h3>
-		<code>
-		var geometry = new THREE.BufferGeometry();
-		// create a simple square shape. We duplicate the top left and bottom right
-		// vertices because each vertex needs to appear once per triangle.
-		var vertexPositions = [
-			[-1.0, -1.0,  1.0],
-			[ 1.0, -1.0,  1.0],
-			[ 1.0,  1.0,  1.0],
-
-			[ 1.0,  1.0,  1.0],
-			[-1.0,  1.0,  1.0],
-			[-1.0, -1.0,  1.0]
-		];
-		var vertices = new Float32Array( vertexPositions.length * 3 ); // three components per vertex
-
-		// components of the position vector for each vertex are stored
-		// contiguously in the buffer.
-		for ( var i = 0; i < vertexPositions.length; i++ )
-		{
-			vertices[ i*3 + 0 ] = vertexPositions[i][0];
-			vertices[ i*3 + 1 ] = vertexPositions[i][1];
-			vertices[ i*3 + 2 ] = vertexPositions[i][2];
-		}
-
-		// itemSize = 3 because there are 3 values (components) per vertex
-		geometry.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
-		var material = new THREE.MeshBasicMaterial( { color: 0xff0000 } );
-		var mesh = new THREE.Mesh( geometry, material );
-		</code>
-		<p>More examples: [example:webgl_buffergeometry Complex mesh with non-indexed faces], [example:webgl_buffergeometry_uint Complex mesh with indexed faces], [example:webgl_buffergeometry_lines Lines], [example:webgl_buffergeometry_lines_indexed Indexed Lines], [example:webgl_buffergeometry_particles Particles], and [example:webgl_buffergeometry_rawshader Raw Shaders].</p>
-
-
-		<h3>Accessing attributes</h3>
-		<p>
-		WebGL stores data associated with individual vertices of a geometry in <emph>attributes</emph>.
-		Examples include the position of the vertex, the normal vector for the vertex, the vertex color,
-		and so on. When using [page:Geometry], the [page:WebGLRenderer renderer] takes care of wrapping
-		up this information into typed array buffers and sending this data to the shader. With
-		BufferGeometry, all of this data is stored in buffers associated with an individual attributes.
-		This means that to get the position data associated with a vertex (for instance), you must call
-		[page:.getAttribute] to access the 'position' [page:BufferAttribute attribute], then access the individual
-		x, y, and z coordinates of the position.
-		</p>
-		<p>
-		The following attributes are set by various members of this class:
-		</p>
-		<h4>[page:BufferAttribute position] (itemSize: 3)</h4>
-		<div>
-		Stores the x, y, and z coordinates of each vertex in this geometry. Set by [page:.fromGeometry]().
-		</div>
-
-		<h4>[page:BufferAttribute normal] (itemSize: 3)</h4>
-		<div>
-		Stores the x, y, and z components of the face or vertex normal vector of each vertex in this geometry.
-		Set by [page:.fromGeometry]().
-		</div>
-
-		<h4>[page:BufferAttribute color] (itemSize: 3)</h4>
-		<div>
-		Stores the red, green, and blue channels of vertex color of each vertex in this geometry.
-		Set by [page:.fromGeometry]().
-		</div>
-
-		<h4>[page:BufferAttribute index] (itemSize: 3)</h4>
-		Allows for vertices to be re-used across multiple triangles; this is called using "indexed triangles," and works much the same as it does in [page:Geometry]: each triangle is associated with the index of three vertices. This attribute therefore stores the index of each vertex for each triangular face.
-
-		If this attribute is not set, the [page:WebGLRenderer renderer] assumes that each three contiguous positions represent a single triangle.
-		</div>
-		<p>
-		In addition to the the built-in attributes, you can set your own custom attributes using the addAttribute method. With [page:Geometry], these attributes are set and stored on the [page:Material]. In BufferGeometry, the attributes are stored with the geometry itself. Note that you still need to set the attributes information on the material as well, but the value of each attribute is stored in the BufferGeometry.
-		</p>
-
-
-		<h2>Constructor</h2>
-
-
-		<h3>[name]()</h3>
-		<div>
-		This creates a new [name]. It also sets several properties to a default value.
-		</div>
-
-
-		<h2>Properties</h2>
-
-		<h3>[property:Integer id]</h3>
-		<div>
-		Unique number for this buffergeometry instance.
-		</div>
-
-		<h3>[property:Hashmap attributes]</h3>
-		<div>
-		This hashmap has as id the name of the attribute to be set and as value the [page:BufferAttribute buffer] to set it to.
-		Rather than accessing this property directly, use addAttribute and getAttribute to access attributes of this geometry.
-		</div>
-
-		<!--
- 		<h3>[property:Boolean dynamic]</h3>
-		<div>
-		When set, it holds certain buffers in memory to have faster updates for this object. When unset, it deletes those buffers and   saves memory.
-		</div> -->
-
-		<h3>[property:Array drawcalls] (previously [property:Array offsets])</h3>
-		<div>
-		For geometries that use indexed triangles, this Array can be used to split the object into multiple WebGL draw calls. Each draw call will draw some subset of the vertices in this geometry using the configured [page:Material shader]. This may be necessary if, for instance, you have more than 65535 vertices in your object.
-		Each element is an object of the form:
-		<code>{ start: Integer, count: Integer, index: Integer }</code>
-		where start specifies the index of the first vertex in this draw call, count specifies how many vertices are included, and index specifies an optional offset.
-
-		Use addDrawCall to add draw calls, rather than modifying this array directly.
-		</div>
-
-		<h3>[property:Box3 boundingBox]</h3>
-		<div>
-		Bounding box.
-		<code>{ min: new THREE.Vector3(), max: new THREE.Vector3() }</code>
-		</div>
-
-		<h3>[property:Sphere boundingSphere]</h3>
-		<div>
-		Bounding sphere.
-		<code>{ radius: float }</code>
-		</div>
-
-		<h3>[property:Array morphTargets]</h3>
-		<div>
-		Array of morph targets. Each morph target is a Javascript object:
-		<code>{ name: "targetName", vertices: [ new THREE.Vertex(), ... ] }</code>
-		Morph vertices match number and order of primary vertices.
-		</div>
-
-		<h2>Methods</h2>
-
-		<h3>[page:EventDispatcher EventDispatcher] methods are available on this class.</h3>
-
-		<h3>[property:null addAttribute]( [page:String name], [page:BufferAttribute attribute] )</h3>
-		<div>
-		Adds an attribute to this geometry. Use this rather than the attributes property,
-		because an internal array of attributes is maintained to speed up iterating over
-		attributes.
-		</div>
-
-		<h3>[method:null addDrawCall]( [page:Integer start], [page:Integer count], [page:Integer indexOffset] )</h3>
-		<div>
-		Adds a draw call to this geometry; see the [page:BufferGeometry.drawcalls drawcalls] property for details.
-		</div>
-
-		<h3>[method:null clearDrawCalls]( )</h3>
-		<div>
-		Clears all draw calls.
-		</div>
-
-		<h3>[method:null applyMatrix]( [page:Matrix4 matrix] )</h3>
-		<div>
-		Bakes matrix transform directly into vertex coordinates.
-		</div>
-
-		<h3>[method:null center] ()</h3>
-		<div>
-		Center the geometry based on the bounding box.
-		</div>
-
-		<h3>[method:BufferGeometry rotateX] ( [page:Float radians] )</h3>
-		<div>
-		Rotate the geometry about the X axis. This is typically done as a one time operation, and not during a loop
-    Use [page:Object3D.rotation] for typical real-time mesh rotation.
-		</div>
-
-		<h3>[method:BufferGeometry rotateY] ( [page:Float radians] )</h3>
-		<div>
-		Rotate the geometry about the Y axis. This is typically done as a one time operation, and not during a loop
-    Use [page:Object3D.rotation] for typical real-time mesh rotation.
-		</div>
-
-		<h3>[method:BufferGeometry rotateZ] ( [page:Float radians] )</h3>
-		<div>
-		Rotate the geometry about the Z axis. This is typically done as a one time operation, and not during a loop
-    Use [page:Object3D.rotation] for typical real-time mesh rotation.
-		</div>
-
-		<h3>[method:BufferGeometry translate] ( [page:Float x], [page:Float y], [page:Float z] )</h3>
-		<div>
-		Translate the geometry. This is typically done as a one time operation, and not during a loop
-    Use [page:Object3D.position] for typical real-time mesh translation.
-		</div>
-
-		<h3>[method:BufferGeometry scale] ( [page:Float x], [page:Float y], [page:Float z] )</h3>
-		<div>
-		Scale the geometry data. This is typically done as a one time operation, and not during a loop
-    Use [page:Object3D.scale] for typical real-time mesh scaling.
-		</div>
-
-		<h3>[method:BufferGeometry lookAt] ( [page:Vector3 vector] )</h3>
-		<div>
-		vector - A world vector to look at.<br />
-		</div>
-		<div>
-		Rotates the geometry to face point in space. This is typically done as a one time operation, and not during a loop
-    Use [page:Object3D.lookAt] for typical real-time mesh usage.
-		</div>
-
-		<h3>[method:BufferGeometry setFromObject] ( [page:Object3D object] )</h3>
-		<div>
-		Sets the attributes for this BufferGeometry from an [page:Object3D].
-		</div>
-
-		<h3>[method:null computeVertexNormals]()</h3>
-		<div>
-		Computes vertex normals by averaging face normals.<br />
-		</div>
-
-		<h3>[method:null computeBoundingBox]()</h3>
-		<div>
-		Computes bounding box of the geometry, updating [page:Geometry Geometry.boundingBox] attribute.<br />
-		Bounding boxes aren't computed by default. They need to be explicitly computed, otherwise they are *null*.
-		</div>
-
-		<h3>[method:null computeBoundingSphere]()</h3>
-		<div>
-		Computes bounding sphere of the geometry, updating [page:Geometry Geometry.boundingSphere] attribute.<br />
-		Bounding spheres aren't computed by default. They need to be explicitly computed, otherwise they are *null*.
-		</div>
-
-		<h3>[method:null computeOffsets] ( [page:Integer size] )</h3>
-		<div>
-		Compute the draw offset for large models by chunking the index buffer into chunks of 65k addressable vertices.
-		This method will effectively rewrite the index buffer and remap all attributes to match the new indices.
-		WARNING: This method will also expand the vertex count to prevent sprawled triangles across draw offsets.
-		size - Defaults to 65535 or 4294967296 if extension OES_element_index_uint supported, but allows for larger or smaller chunks.
-		</div>
-
-		<h3>[method:null merge]( [page:BufferGeometry bufferGeometry], [page:Integer offset] )</h3>
-		<div>
-		Merge in another BufferGeometry with an optional offset of where to start merging in.
-		</div>
-
-		<h3>[method:null dispose]()</h3>
-		<div>
-		Disposes the object from memory. <br />
-		You need to call this when you want the bufferGeometry removed while the application is running.
-		</div>
-
-		<h3>[method:null fromGeometry]( [page:Geometry] )</h3>
-		<div>
-		Populates this BufferGeometry with data from a [page:Geometry] object.
-		</div>
-
-		<h3>[method:BufferAttribute getAttribute]( [page:String name] )</h3>
-		<div>
-		Returns the [page:BufferAttribute attribute] with the specified name.
-		</div>
-
-		<h3>[method:BufferAttribute removeAttribute]( [page:String name] )</h3>
-		<div>
-		Removes the [page:BufferAttribute attribute] with the specified name.
-		</div>
-
-		<h3>[method:null normalizeNormals]()</h3>
-		<div>
-		Every normal vector in a geometry will have a magnitude of 1.
-		This will correct lighting on the geometry surfaces.
-		</div>
-
-		<h3>[method:Object toJSON]()</h3>
-		<div>
-		Returns a raw object representation of the BufferGeometry.
-		</div>
-
-		<h3>[method:BufferGeometry clone]()</h3>
-		<div>
-		Creates a clone of this BufferGeometry.
-		</div>
-
-		<h3>[method:BufferGeometry copy]( [page:BufferGeometry bufferGeometry] )</h3>
-		<div>
-		Copies another BufferGeometry to this BufferGeometry.
-		</div>
-
-
-
-		<h2>Source</h2>
-
-		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
-	</body>
-</html>
+		<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">
+		<p>
+		This class is an efficient alternative to [page:Geometry], because it stores all data, including
+		vertex positions, face indices, normals, colors, UVs, and custom attributes within buffers; this
+		reduces the cost of passing all this data to the GPU.
+		This also makes BufferGeometry harder to work with than [page:Geometry]; rather than accessing
+		position data as [page:Vector3] objects, color data as [page:Color] objects, and so on, you have to
+		access the raw data from the appropriate [page:BufferAttribute attribute] buffer. This makes
+		BufferGeometry best-suited for static objects where you don't need to manipulate the geometry much
+		after instantiating it.
+		</p>
+
+		<h3>Example</h3>
+		<code>
+		var geometry = new THREE.BufferGeometry();
+		// create a simple square shape. We duplicate the top left and bottom right
+		// vertices because each vertex needs to appear once per triangle.
+		var vertexPositions = [
+			[-1.0, -1.0,  1.0],
+			[ 1.0, -1.0,  1.0],
+			[ 1.0,  1.0,  1.0],
+
+			[ 1.0,  1.0,  1.0],
+			[-1.0,  1.0,  1.0],
+			[-1.0, -1.0,  1.0]
+		];
+		var vertices = new Float32Array( vertexPositions.length * 3 ); // three components per vertex
+
+		// components of the position vector for each vertex are stored
+		// contiguously in the buffer.
+		for ( var i = 0; i < vertexPositions.length; i++ )
+		{
+			vertices[ i*3 + 0 ] = vertexPositions[i][0];
+			vertices[ i*3 + 1 ] = vertexPositions[i][1];
+			vertices[ i*3 + 2 ] = vertexPositions[i][2];
+		}
+
+		// itemSize = 3 because there are 3 values (components) per vertex
+		geometry.addAttribute( 'position', new THREE.BufferAttribute( vertices, 3 ) );
+		var material = new THREE.MeshBasicMaterial( { color: 0xff0000 } );
+		var mesh = new THREE.Mesh( geometry, material );
+		</code>
+		<p>More examples: [example:webgl_buffergeometry Complex mesh with non-indexed faces], [example:webgl_buffergeometry_uint Complex mesh with indexed faces], [example:webgl_buffergeometry_lines Lines], [example:webgl_buffergeometry_lines_indexed Indexed Lines], [example:webgl_buffergeometry_custom_attributes_particles Particles], and [example:webgl_buffergeometry_rawshader Raw Shaders].</p>
+
+
+		<h3>Accessing attributes</h3>
+		<p>
+		WebGL stores data associated with individual vertices of a geometry in <emph>attributes</emph>.
+		Examples include the position of the vertex, the normal vector for the vertex, the vertex color,
+		and so on. When using [page:Geometry], the [page:WebGLRenderer renderer] takes care of wrapping
+		up this information into typed array buffers and sending this data to the shader. With
+		BufferGeometry, all of this data is stored in buffers associated with an individual attributes.
+		This means that to get the position data associated with a vertex (for instance), you must call
+		[page:.getAttribute] to access the 'position' [page:BufferAttribute attribute], then access the individual
+		x, y, and z coordinates of the position.
+		</p>
+		<p>
+		The following attributes are set by various members of this class:
+		</p>
+		<h4>[page:BufferAttribute position] (itemSize: 3)</h4>
+		<div>
+		Stores the x, y, and z coordinates of each vertex in this geometry. Set by [page:.fromGeometry]().
+		</div>
+
+		<h4>[page:BufferAttribute normal] (itemSize: 3)</h4>
+		<div>
+		Stores the x, y, and z components of the face or vertex normal vector of each vertex in this geometry.
+		Set by [page:.fromGeometry]().
+		</div>
+
+		<h4>[page:BufferAttribute color] (itemSize: 3)</h4>
+		<div>
+		Stores the red, green, and blue channels of vertex color of each vertex in this geometry.
+		Set by [page:.fromGeometry]().
+		</div>
+
+		<h4>[page:BufferAttribute index] (itemSize: 3)</h4>
+		Allows for vertices to be re-used across multiple triangles; this is called using "indexed triangles," and works much the same as it does in [page:Geometry]: each triangle is associated with the index of three vertices. This attribute therefore stores the index of each vertex for each triangular face.
+
+		If this attribute is not set, the [page:WebGLRenderer renderer] assumes that each three contiguous positions represent a single triangle.
+		</div>
+		<p>
+		In addition to the the built-in attributes, you can set your own custom attributes using the addAttribute method. With [page:Geometry], these attributes are set and stored on the [page:Material]. In BufferGeometry, the attributes are stored with the geometry itself. Note that you still need to set the attributes information on the material as well, but the value of each attribute is stored in the BufferGeometry.
+		</p>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]()</h3>
+		<div>
+		This creates a new [name]. It also sets several properties to a default value.
+		</div>
+
+
+		<h2>Properties</h2>
+
+		<h3>[property:Integer id]</h3>
+		<div>
+		Unique number for this buffergeometry instance.
+		</div>
+
+		<h3>[property:Hashmap attributes]</h3>
+		<div>
+		This hashmap has as id the name of the attribute to be set and as value the [page:BufferAttribute buffer] to set it to.
+		Rather than accessing this property directly, use addAttribute and getAttribute to access attributes of this geometry.
+		</div>
+
+		<!--
+ 		<h3>[property:Boolean dynamic]</h3>
+		<div>
+		When set, it holds certain buffers in memory to have faster updates for this object. When unset, it deletes those buffers and   saves memory.
+		</div> -->
+
+		<h3>[property:Array drawcalls] (previously [property:Array offsets])</h3>
+		<div>
+		For geometries that use indexed triangles, this Array can be used to split the object into multiple WebGL draw calls. Each draw call will draw some subset of the vertices in this geometry using the configured [page:Material shader]. This may be necessary if, for instance, you have more than 65535 vertices in your object.
+		Each element is an object of the form:
+		<code>{ start: Integer, count: Integer, index: Integer }</code>
+		where start specifies the index of the first vertex in this draw call, count specifies how many vertices are included, and index specifies an optional offset.
+
+		Use addDrawCall to add draw calls, rather than modifying this array directly.
+		</div>
+
+		<h3>[property:Box3 boundingBox]</h3>
+		<div>
+		Bounding box.
+		<code>{ min: new THREE.Vector3(), max: new THREE.Vector3() }</code>
+		</div>
+
+		<h3>[property:Sphere boundingSphere]</h3>
+		<div>
+		Bounding sphere.
+		<code>{ radius: float }</code>
+		</div>
+
+		<h2>Methods</h2>
+
+		<h3>[page:EventDispatcher EventDispatcher] methods are available on this class.</h3>
+
+		<h3>[property:null addAttribute]( [page:String name], [page:BufferAttribute attribute] )</h3>
+		<div>
+		Adds an attribute to this geometry. Use this rather than the attributes property,
+		because an internal array of attributes is maintained to speed up iterating over
+		attributes.
+		</div>
+
+		<h3>[method:null addDrawCall]( [page:Integer start], [page:Integer count], [page:Integer indexOffset] )</h3>
+		<div>
+		Adds a draw call to this geometry; see the [page:BufferGeometry.drawcalls drawcalls] property for details.
+		</div>
+
+		<h3>[method:null clearDrawCalls]( )</h3>
+		<div>
+		Clears all draw calls.
+		</div>
+
+		<h3>[method:null applyMatrix]( [page:Matrix4 matrix] )</h3>
+		<div>
+		Bakes matrix transform directly into vertex coordinates.
+		</div>
+
+		<h3>[method:null center] ()</h3>
+		<div>
+		Center the geometry based on the bounding box.
+		</div>
+
+		<h3>[method:BufferGeometry rotateX] ( [page:Float radians] )</h3>
+		<div>
+		Rotate the geometry about the X axis. This is typically done as a one time operation, and not during a loop
+    Use [page:Object3D.rotation] for typical real-time mesh rotation.
+		</div>
+
+		<h3>[method:BufferGeometry rotateY] ( [page:Float radians] )</h3>
+		<div>
+		Rotate the geometry about the Y axis. This is typically done as a one time operation, and not during a loop
+    Use [page:Object3D.rotation] for typical real-time mesh rotation.
+		</div>
+
+		<h3>[method:BufferGeometry rotateZ] ( [page:Float radians] )</h3>
+		<div>
+		Rotate the geometry about the Z axis. This is typically done as a one time operation, and not during a loop
+    Use [page:Object3D.rotation] for typical real-time mesh rotation.
+		</div>
+
+		<h3>[method:BufferGeometry translate] ( [page:Float x], [page:Float y], [page:Float z] )</h3>
+		<div>
+		Translate the geometry. This is typically done as a one time operation, and not during a loop
+    Use [page:Object3D.position] for typical real-time mesh translation.
+		</div>
+
+		<h3>[method:BufferGeometry scale] ( [page:Float x], [page:Float y], [page:Float z] )</h3>
+		<div>
+		Scale the geometry data. This is typically done as a one time operation, and not during a loop
+    Use [page:Object3D.scale] for typical real-time mesh scaling.
+		</div>
+
+		<h3>[method:BufferGeometry lookAt] ( [page:Vector3 vector] )</h3>
+		<div>
+		vector - A world vector to look at.<br />
+		</div>
+		<div>
+		Rotates the geometry to face point in space. This is typically done as a one time operation, and not during a loop
+    Use [page:Object3D.lookAt] for typical real-time mesh usage.
+		</div>
+
+		<h3>[method:BufferGeometry setFromObject] ( [page:Object3D object] )</h3>
+		<div>
+		Sets the attributes for this BufferGeometry from an [page:Object3D].
+		</div>
+
+		<h3>[method:null computeVertexNormals]()</h3>
+		<div>
+		Computes vertex normals by averaging face normals.<br />
+		</div>
+
+		<h3>[method:null computeBoundingBox]()</h3>
+		<div>
+		Computes bounding box of the geometry, updating [page:Geometry Geometry.boundingBox] attribute.<br />
+		Bounding boxes aren't computed by default. They need to be explicitly computed, otherwise they are *null*.
+		</div>
+
+		<h3>[method:null computeBoundingSphere]()</h3>
+		<div>
+		Computes bounding sphere of the geometry, updating [page:Geometry Geometry.boundingSphere] attribute.<br />
+		Bounding spheres aren't computed by default. They need to be explicitly computed, otherwise they are *null*.
+		</div>
+
+		<h3>[method:null computeOffsets] ( [page:Integer size] )</h3>
+		<div>
+		Compute the draw offset for large models by chunking the index buffer into chunks of 65k addressable vertices.
+		This method will effectively rewrite the index buffer and remap all attributes to match the new indices.
+		WARNING: This method will also expand the vertex count to prevent sprawled triangles across draw offsets.
+		size - Defaults to 65535 or 4294967296 if extension OES_element_index_uint supported, but allows for larger or smaller chunks.
+		</div>
+
+		<h3>[method:null merge]( [page:BufferGeometry bufferGeometry], [page:Integer offset] )</h3>
+		<div>
+		Merge in another BufferGeometry with an optional offset of where to start merging in.
+		</div>
+
+		<h3>[method:null dispose]()</h3>
+		<div>
+		Disposes the object from memory. <br />
+		You need to call this when you want the bufferGeometry removed while the application is running.
+		</div>
+
+		<h3>[method:null fromGeometry]( [page:Geometry] )</h3>
+		<div>
+		Populates this BufferGeometry with data from a [page:Geometry] object.
+		</div>
+
+		<h3>[method:BufferAttribute getAttribute]( [page:String name] )</h3>
+		<div>
+		Returns the [page:BufferAttribute attribute] with the specified name.
+		</div>
+
+		<h3>[method:BufferAttribute removeAttribute]( [page:String name] )</h3>
+		<div>
+		Removes the [page:BufferAttribute attribute] with the specified name.
+		</div>
+
+		<h3>[method:null normalizeNormals]()</h3>
+		<div>
+		Every normal vector in a geometry will have a magnitude of 1.
+		This will correct lighting on the geometry surfaces.
+		</div>
+
+		<h3>[method:Object toJSON]()</h3>
+		<div>
+		Returns a raw object representation of the BufferGeometry.
+		</div>
+
+		<h3>[method:BufferGeometry clone]()</h3>
+		<div>
+		Creates a clone of this BufferGeometry.
+		</div>
+
+		<h3>[method:BufferGeometry copy]( [page:BufferGeometry bufferGeometry] )</h3>
+		<div>
+		Copies another BufferGeometry to this BufferGeometry.
+		</div>
+
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>

+ 1 - 1
docs/api/core/Face3.html

@@ -74,7 +74,7 @@
 
 
 		<h3>[property:Integer materialIndex]</h3>
 		<h3>[property:Integer materialIndex]</h3>
 		<div>
 		<div>
-		Material index (points to [page:MeshFaceMaterial MeshFaceMaterial.materials]).
+		Material index (points to [page:MultiMaterial MultiMaterial.materials]).
 		</div>
 		</div>
 
 
 		<h2>Methods</h2>
 		<h2>Methods</h2>

+ 26 - 8
docs/api/core/Geometry.html

@@ -96,16 +96,32 @@
 
 
 		<h3>[property:Array skinWeights]</h3>
 		<h3>[property:Array skinWeights]</h3>
 		<div>
 		<div>
-		Array of [page:Vector4 Vector4s] representing the skinning weights as used in a [page:SkinnedMesh],
-		The weights match the number and order of vertices in the geometry. The weighted values
-		are typically between the values of 0 and 1 and affect the amount that the individuals bones affect
-		a given vertex.
+		When working with a [page:SkinnedMesh], each vertex can have up to 4 [page:Bone bones] affecting it.
+		The skinWeights property is an array of weight values that correspond to the order of the vertices in
+		the geometry. So for instance, the first skinWeight would correspond to the first vertex in the geometry.
+		Since each vertex can be modified by 4 bones, a [page:Vector4] is used to represent the skin weights
+		for that vertex.
+		</div>
+		<div>
+		The values of the vector should typically be between 0 and 1. For instance when set to 0 the bone
+		transformation will have no affect. When set to 0.5 it will have 50% affect. When set to 100%, it will
+		have 100% affect. If there is only 1 bone associated with the vertex then you only need to worry about
+		the first component of the vector, the rest can be ignored and set to 0.
 		</div>
 		</div>
 
 
 		<h3>[property:Array skinIndices]</h3>
 		<h3>[property:Array skinIndices]</h3>
 		<div>
 		<div>
-		Array of [page:Vector4 Vector4s] representing the indices of individual bones in the [page:Skeleton.bones] array,
-		The indices match the number and order of vertices in the geometry.
+		Just like the skinWeights property, the skinIndices' values correspond to the geometry's vertices.
+		Each vertex can have up to 4 bones associated with it. So if you look at the first vertex, and the
+		first skinIndex, this will tell you the bones associated with that vertex. For example the first vertex
+		could have a value of <strong>( 10.05, 30.10, 12.12 )</strong>. Then the first skin index could have the
+		value of <strong>( 10, 2, 0, 0 )</strong>. The first skin weight could have the value of
+		<strong>( 0.8, 0.2, 0, 0 )</strong>. In affect this would take the first vertex, and then the bone
+		<strong>mesh.bones[10]</strong> and apply it 80% of the way. Then it would take the bone <strong>skeleton.bones[2]</strong>
+		and apply it 20% of the way. The next two values have a weight of 0, so they would have no affect.
+		</div>
+		<div>
+		In code another example could look like this:
 		<code>
 		<code>
 		// e.g.
 		// e.g.
 		geometry.skinIndices[15] = new THREE.Vector4(   0,   5,   9, 0 );
 		geometry.skinIndices[15] = new THREE.Vector4(   0,   5,   9, 0 );
@@ -118,7 +134,7 @@
 		skeleton.bones[0]; // weight of 0.2
 		skeleton.bones[0]; // weight of 0.2
 		skeleton.bones[5]; // weight of 0.5
 		skeleton.bones[5]; // weight of 0.5
 		skeleton.bones[9]; // weight of 0.3
 		skeleton.bones[9]; // weight of 0.3
-		skeleton.bones[0]; // weight of 0
+		skeleton.bones[10]; // weight of 0
 		</code>
 		</code>
 		</div>
 		</div>
 
 
@@ -271,13 +287,15 @@
 		<h3>[method:null normalize]()</h3>
 		<h3>[method:null normalize]()</h3>
 		<div>
 		<div>
 		Normalize the geometry. <br />
 		Normalize the geometry. <br />
-		Make the geometry centered and has a bounding sphere whose raidus equals to 1.0.
+		Make the geometry centered and has a bounding sphere whose radius equals to 1.0.
 		</div>
 		</div>
 
 
 		<h3>[method:Geometry clone]()</h3>
 		<h3>[method:Geometry clone]()</h3>
 		<div>
 		<div>
 		Creates a new clone of the Geometry.
 		Creates a new clone of the Geometry.
 		</div>
 		</div>
+		
+		<div>This method copies only vertices, faces and uvs. It does not copy any other properties of the geometry.</div>
 
 
 		<h3>[method:null dispose]()</h3>
 		<h3>[method:null dispose]()</h3>
 		<div>
 		<div>

+ 1 - 1
docs/api/core/Raycaster.html

@@ -56,7 +56,7 @@
 			[example:webgl_interactive_cubes_ortho Raycasting to a Mesh in using an OrthographicCamera], 
 			[example:webgl_interactive_cubes_ortho Raycasting to a Mesh in using an OrthographicCamera], 
 			[example:webgl_interactive_buffergeometry Raycasting to a Mesh with BufferGeometry], 
 			[example:webgl_interactive_buffergeometry Raycasting to a Mesh with BufferGeometry], 
 			[example:webgl_interactive_lines Raycasting to a Line], 
 			[example:webgl_interactive_lines Raycasting to a Line], 
-			[example:webgl_interactive_raycasting_pointcloud Raycasting to a Points], 
+			[example:webgl_interactive_raycasting_points Raycasting to Points], 
 			[example:webgl_geometry_terrain_raycast Terrain raycasting], 
 			[example:webgl_geometry_terrain_raycast Terrain raycasting], 
 			[example:webgl_octree_raycasting Raycasting using an octree],
 			[example:webgl_octree_raycasting Raycasting using an octree],
 			[example:webgl_interactive_voxelpainter Raycasting to paint voxels]</div>
 			[example:webgl_interactive_voxelpainter Raycasting to paint voxels]</div>

+ 2 - 2
docs/api/materials/SpriteCanvasMaterial.html → docs/api/examples/SpriteCanvasMaterial.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="en">
 <html lang="en">
 	<head>
 	<head>
-		<meta charset="utf-8" />
+		<meta charset="utf-8" />
 		<base href="../../" />
 		<base href="../../" />
 		<script src="list.js"></script>
 		<script src="list.js"></script>
 		<script src="page.js"></script>
 		<script src="page.js"></script>
@@ -52,6 +52,6 @@
 
 
 		<h2>Source</h2>
 		<h2>Source</h2>
 
 
-		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+		[link:https://github.com/mrdoob/three.js/blob/master/examples/js/renderers/CanvasRenderer.js examples/js/renderers/CanvasRenderer.js]
 	</body>
 	</body>
 </html>
 </html>

+ 0 - 115
docs/api/extras/FontUtils.html

@@ -1,115 +0,0 @@
-<!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">A class for text operations in three.js (See [page:TextGeometry])</div>
-
-
-		<h2>Properties</h2>
-
-
-
-		<h3>[property:number divisions]</h3>
-		<div>
-		The amount of segments in a curve. Default is 10.
-		</div> 
-
-		<h3>[property:string style]</h3>
-		<div>
-		The style of the used font. Default is "normal".
-		</div> 
-
-		<h3>[property:string weight]</h3>
-		<div>
-		The weight of the used font. Default is "normal".
-		</div> 
-
-		<h3>[property:string face]</h3>
-		<div>
-		The name of the font. Default is "helvetiker".
-		</div> 
-
-		<h3>[property:object faces]</h3>
-		<div>
-		All Fonts which are already loaded in.
-		</div> 
-
-		<h3>[property:number size]</h3>
-		<div>
-		The size of the used Font. Default is 150.
-		</div> 
-
-		<h2>Methods</h2>
-
-
-
-		<h3>[method:Object drawText]([page:string text])</h3>
-		<div>
-		text -- The text to draw.
-		</div>
-		<div>
-		Calculates the path and offset of the text in the used font. It returns an  object like { paths : fontPaths, offset : width }.
-		</div>
-
-		<h3>[method:Array Triangulate]([page:Array contour], [page:Boolean indices])</h3>
-		<div>
-		contour -- Array of vector2 to define an contour <br />
-		indices -- A boolean indicating if you need to return indices.
-		</div>
-		<div>
-		Triangulates a contour into an array of faces.
-		</div>
-
-		<h3>[method:Object extractGlyphPoints]([page:string c], [page:string face], [page:number scale], [page:number offset], [page:Path path])</h3>
-		<div>
-		c -- The character to extract. <br />
-		face -- The face to use. <br />
-		scale -- The scale of the character. <br />
-		offset -- The offset of the character compared to begin of the path. <br />
-		path -- The path to which to add the character points.
-		</div>
-		<div>
-		This ectracts the glyphPoints of the character of the face and returns an object containing the path and the new offset.
-		</div>
-
-		<h3>[method:Array generateShapes]([page:string text], [page:Object parameters])</h3>
-		<div>
-		text -- The text to generate the shapes from.<br />
-		parameters -- The parameter containing <br />
-			size -- Default is 100.
-			curveSegments -- Default is 4.
-			font -- Default is "helvetiker".
-			weight -- Default is "normal".
-			style -- Default is "normal".
-
-		</div>
-		<div>
-		Generates shapes from the text and return them as an Array of [page:Shape].
-		</div>
-
-		<h3>[method:Object loadFace]([page:Object data])</h3>
-		<div>
-		data -- The data of the face.
-		</div>
-		<div>
-		This loads and saves the data of the face and return the data. When you add the font Data as javascriptfile, then this automatically get called. So there is no need to do this yourself.
-		</div>
-
-		<h3>[method:Object getFace]()</h3>
-		<div>
-		Returns the used font its data based on its style and weight.
-		</div>
-
-		<h2>Source</h2>
-
-		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
-	</body>
-</html>

+ 0 - 88
docs/api/extras/ImageUtils.html

@@ -1,88 +0,0 @@
-<!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">A Helper class to ease the loading of images of different types.</div>
-		
-
-		<h2>Properties</h2>
-
-
-
-		<h3>[property:string crossOrigin]</h3>
-		<div>
-		The crossOrigin string to implement CORS for loading the image from a different domain that allows CORS.
-		</div> 
-
-		<h2>Methods</h2>
-
-
-
-		<h3>[method:DataTexture generateDataTexture]([page:Number width], [page:Number height], [page:Number color])</h3>
-		<div>
-		width -- The width of the texture. <br />
-		height -- The height of the texture. <br />
-		color -- The hexadecimal value of the color.
-		</div>
-		<div>
-		Generates a texture of a single color. It is a DataTexture with format, RGBFormat.
-		</div>
-
-		<h3>[method:CompressedTexture parseDDS]([page:String buffer], [page:boolean loadMipmaps])</h3>
-		<div>
-		buffer -- A string containing the data of the dds. <br />
-		loadMipmaps -- A boolean to indicate if you need to load the mipmaps. Default is True.
-		</div>
-		<div>
-		Parses a DDS Image from the string into a CompressedTexture. 
-		</div>
-
-		<h3>[method:Texture loadTexture]([page:String url], [page:UVMapping mapping], [page:Function onLoad], [page:Function onError])</h3>
-		<div>
-		url -- the url of the texture<br />
-		mapping -- Can be an instance of [page:UVMapping THREE.UVMapping], [page:CubeReflectionMapping THREE.CubeReflectionMapping] or [page:SphericalReflectionMapping THREE.SphericalReflectionMapping]. Describes how the image is applied to the object.<br />Use undefined instead of null as a default value. See mapping property of [page:Texture texture] for more details. <br/>
-		onLoad -- callback function<br />
-		onError -- callback function
-		</div>
-		<div>
-		A helper function to generates a [page:Texture THREE.Texture] from an image URL. Provides a load and error
-		callback.
-		</div>
-
-		<h3>[method:canvas getNormalMap]([page:Image image], [page:Float depth])</h3>
-		<div>
-		image -- A loaded image element <br />
-		depth -- The depth of the normal map. Defaults to between -1 and 1.
-		</div>
-		<div>
-		Translates an image element into a normal map with the range (-1, -1, -1) to (1, 1, 1) multiplied by the depth.
-		Returns a canvas element.
-		</div>
-
-		<h3>[method:CubeTexture loadTextureCube]([page:Array array], [page:Textures mapping], [page:function onLoad], [page:function onError])</h3>
-		<div>
-		array -- An array of 6 images <br />
-		mapping -- Either [page:Textures THREE.CubeReflectionMapping] or [page:Textures THREE.CubeRefractionMapping]<br />
-		onLoad -- callback <br />
-		onError -- callback
-		</div>
-		<div>
-		Creates a [page:CubeTexture] from 6 images.<br /><br />
-		
-		The images are loaded in the following order where p is positiive and n is negative: [ px, nx, py, ny, pz, nz ].
-		See [page:CubeTexture] for an example in code.
-		</div>
-
-		<h2>Source</h2>
-
-		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
-	</body>
-</html>

+ 1 - 1
docs/api/extras/SceneUtils.html

@@ -22,7 +22,7 @@
 		materials -- The materials for the object.
 		materials -- The materials for the object.
 		</div>
 		</div>
 		<div>
 		<div>
-		Creates an new Object3D an new mesh for each material defined in materials. Beware that this is not the same as Meshfacematerial which defines multiple material for 1 mesh.<br />
+		Creates an new Object3D an new mesh for each material defined in materials. Beware that this is not the same as MultiMaterial which defines multiple material for 1 mesh.<br />
 		This is mostly useful for object that need a material and a wireframe implementation.
 		This is mostly useful for object that need a material and a wireframe implementation.
 		</div>
 		</div>
 
 

+ 60 - 0
docs/api/extras/curves/CatmullRomCurve3.html

@@ -0,0 +1,60 @@
+<!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>
+		[page:Curve] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">Create a smooth 3d spline curve from a series of points using the Catmull-Rom algorithm</div>
+
+		<h2>Example</h2>
+
+<code>
+//Create a closed wavey loop
+var curve = new THREE.CatmullRomCurve3( [
+	new THREE.Vector3( -10, 0, 10 ),
+	new THREE.Vector3( -5, 5, 5 ),
+	new THREE.Vector3( 0, 0, 0 ),
+	new THREE.Vector3( 5, -5, 5 ),
+	new THREE.Vector3( 10, 0, 10 )
+] );
+
+var geometry = new THREE.Geometry();
+geometry.vertices = curve.getPoints( 50 );
+
+var material = new THREE.LineBasicMaterial( { color : 0xff0000 } );
+</code>
+
+		<h3>[example:webgl_geometry_extrude_splines geometry / extrude / splines]</h3>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]( [page:Array points] )</h3>
+		<div>points – An array of [page:Vector3] points</div>
+
+		<h2>Properties</h2>
+
+		<h3>[property:Array points]</h3>
+		
+		<h3>[property:Boolean closed] – curve loops back onto itself when true. False by default.</h3>
+		
+		<h3>[property:String type] - possible values are `centripetal` (default), `chordal` and `catmullrom`
+			
+		<h3>[property:float tension] - when type is `catmullrom`, defines catmullrom's tension. Defaults to 0.5
+
+		<h2>Methods</h2>
+
+		<h3>See [page:Curve] for inherited methods</h3>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>

+ 2 - 2
docs/api/extras/curves/ClosedSplineCurve3.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="en">
 <html lang="en">
 	<head>
 	<head>
-		<meta charset="utf-8" />
+		<meta charset="utf-8" />
 		<base href="../../../" />
 		<base href="../../../" />
 		<script src="list.js"></script>
 		<script src="list.js"></script>
 		<script src="page.js"></script>
 		<script src="page.js"></script>
@@ -12,7 +12,7 @@
 
 
 		<h1>[name]</h1>
 		<h1>[name]</h1>
 
 
-		<div class="desc">Create a smooth 3d spline curve from a series of points that loops back onto itself</div>
+		<div class="desc">Create a smooth 3d spline curve from a series of points that loops back onto itself. THREE.ClosedSplineCurve3 has been deprecated. Please use THREE.CatmullRomCurve3</div>
 
 
 		<h2>Example</h2>
 		<h2>Example</h2>
 
 

+ 0 - 20
docs/api/extras/geometries/CubeGeometry.html

@@ -1,20 +0,0 @@
-<!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>
-		[page:Geometry] &rarr;
-
-		<h1>[name]</h1>
-
-		<div class="desc">Renamed CubeGeometry to BoxGeometry. see [page:BoxGeometry].</div>
-
-
-		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
-	</body>
-</html>

+ 4 - 4
docs/api/extras/geometries/LatheGeometry.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="en">
 <html lang="en">
 	<head>
 	<head>
-		<meta charset="utf-8" />
+		<meta charset="utf-8" />
 		<base href="../../../" />
 		<base href="../../../" />
 		<script src="list.js"></script>
 		<script src="list.js"></script>
 		<script src="page.js"></script>
 		<script src="page.js"></script>
@@ -12,7 +12,7 @@
 
 
 		<h1>[name]</h1>
 		<h1>[name]</h1>
 
 
-		<div class="desc">Class for generating meshes with axial symmetry. Possible uses include donuts, pipes, vases etc. The lathe rotate around the Z axis.</div>
+		<div class="desc">Class for generating meshes with axial symmetry. Possible uses include donuts, pipes, vases etc. The lathe rotate around the Y axis.</div>
 
 
 
 
 		<h2>Example</h2>
 		<h2>Example</h2>
@@ -20,7 +20,7 @@
 		<code>
 		<code>
 		var points = [];
 		var points = [];
 		for ( var i = 0; i < 10; i ++ ) {
 		for ( var i = 0; i < 10; i ++ ) {
-			points.push( new THREE.Vector3( Math.sin( i * 0.2 ) * 15 + 50, 0, ( i - 5 ) * 2 ) );
+			points.push( new THREE.Vector2( Math.sin( i * 0.2 ) * 15 + 50, ( i - 5 ) * 2 ) );
 
 
 		}
 		}
 		var geometry = new THREE.LatheGeometry( points );
 		var geometry = new THREE.LatheGeometry( points );
@@ -34,7 +34,7 @@
 
 
 		<h3>[name]([page:Array points], [page:Integer segments], [page:Float phiStart], [page:Float phiLength])</h3>
 		<h3>[name]([page:Array points], [page:Integer segments], [page:Float phiStart], [page:Float phiLength])</h3>
 		<div>
 		<div>
-		points — Array of Vector3s. Since this rotates around Z axis, the y-values can be set to 0<br />
+		points — Array of Vector2s.<br />
 		segments — the number of circumference segments to generate. Default is 12.<br />
 		segments — the number of circumference segments to generate. Default is 12.<br />
 		phiStart — the starting angle in radians. Default is 0.<br />
 		phiStart — the starting angle in radians. Default is 0.<br />
 		phiLength — the radian (0 to 2*PI) range of the lathed section 2*PI is a closed lathe, less than 2PI is a portion. Default is 2*PI
 		phiLength — the radian (0 to 2*PI) range of the lathed section 2*PI is a closed lathe, less than 2PI is a portion. Default is 2*PI

+ 1 - 3
docs/api/extras/geometries/TextGeometry.html

@@ -31,12 +31,10 @@
 		text — The text that needs to be shown. <br />
 		text — The text that needs to be shown. <br />
 		parameters — Object that can contains the following parameters.
 		parameters — Object that can contains the following parameters.
 		<ul>
 		<ul>
+			<li>font — THREE.Font.</li>
 			<li>size — Float. Size of the text.</li>
 			<li>size — Float. Size of the text.</li>
 			<li>height — Float. Thickness to extrude text.  Default is 50.</li>
 			<li>height — Float. Thickness to extrude text.  Default is 50.</li>
 			<li>curveSegments — Integer. Number of points on the curves. Default is 12.</li>
 			<li>curveSegments — Integer. Number of points on the curves. Default is 12.</li>
-			<li>font — String. Font name.</li>
-			<li>weight — String. Font weight (normal, bold).</li>
-			<li>style —  String. Font style (normal, italics).</li>
 			<li>bevelEnabled — Boolean. Turn on bevel. Default is False.</li>
 			<li>bevelEnabled — Boolean. Turn on bevel. Default is False.</li>
 			<li>bevelThickness — Float. How deep into text bevel goes. Default is 10.</li>
 			<li>bevelThickness — Float. How deep into text bevel goes. Default is 10.</li>
 			<li>bevelSize — Float. How far from text outline is bevel. Default is 8.</li>
 			<li>bevelSize — Float. How far from text outline is bevel. Default is 8.</li>

+ 2 - 1
docs/api/lights/SpotLight.html

@@ -70,7 +70,8 @@
 		<h3>[property:Object3D target]</h3>
 		<h3>[property:Object3D target]</h3>
 		<div>
 		<div>
 			Spotlight focus points at target.position.<br />
 			Spotlight focus points at target.position.<br />
-			Default position — *(0,0,0)*.
+			Default position — *(0,0,0)*.<br />
+			*Note*: Currently for target property to work properly, it must be part of the [page:Scene scene], e.g. this will help: <code>scene.add( light.target )</code>
 		</div>
 		</div>
 	
 	
 		<h3>[property:Float intensity]</h3>
 		<h3>[property:Float intensity]</h3>

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

@@ -61,7 +61,7 @@
 			'models/animated/monster/monster.js',
 			'models/animated/monster/monster.js',
 			// Function when resource is loaded
 			// Function when resource is loaded
 			function ( geometry, materials ) {
 			function ( geometry, materials ) {
-				var material = new THREE.MeshFaceMaterial( materials );
+				var material = new THREE.MultiMaterial( materials );
 				var object = new THREE.Mesh( geometry, material );
 				var object = new THREE.Mesh( geometry, material );
 				scene.add( object );
 				scene.add( object );
 			}
 			}

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

@@ -19,7 +19,7 @@
 		<h3>[name]( [page:String baseUrl], [page:Object options], [page:String crossOrigin] )</h3>
 		<h3>[name]( [page:String baseUrl], [page:Object options], [page:String crossOrigin] )</h3>
 		<div>
 		<div>
 		[page:String baseUrl] — The base url from which to find subsequent resources.<br />
 		[page:String baseUrl] — The base url from which to find subsequent resources.<br />
-		[page:Object options] — Options passed to the created material (side, wrap, normalizeRGB, ignoreZeroRGBs, invertTransparency).<br />
+		[page:Object options] — Options passed to the created material (side, wrap, normalizeRGB, ignoreZeroRGBs).<br />
 		[page:String crossOrigin] — The crossOrigin string to implement CORS for loading the url from a different domain that allows CORS.<br />
 		[page:String crossOrigin] — The crossOrigin string to implement CORS for loading the url from a different domain that allows CORS.<br />
 		</div>
 		</div>
 		<div>
 		<div>

+ 0 - 8
docs/api/loaders/MaterialLoader.html

@@ -37,14 +37,6 @@
 		Begin loading from url and return the [page:Material] object that will contain the data.
 		Begin loading from url and return the [page:Material] object that will contain the data.
 		</div>
 		</div>
 
 
-		<h3>[method:null setCrossOrigin]( [page:String value] )</h3>
-		<div>
-		[page:String value] — The crossOrigin string.
-		</div>
-		<div>
-		The crossOrigin string to implement CORS for loading the url from a different domain that allows CORS.
-		</div>
-
 		<h3>[method:Material parse]( [page:Object json] )</h3>
 		<h3>[method:Material parse]( [page:Object json] )</h3>
 		<div>
 		<div>
 		[page:Object json] — The json object containing the parameters of the Material.
 		[page:Object json] — The json object containing the parameters of the Material.

+ 84 - 0
docs/api/loaders/PCDLoader.html

@@ -0,0 +1,84 @@
+<!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">A loader for <em>PCD</em> files. Loads ascii and binary. Compressed binary files are not suported.</div>
+
+
+		<h2>Constructor</h2>
+
+		<h3>[name]( [page:LoadingManager manager] )</h3>
+		<div>
+		[page:LoadingManager manager] — The [page:LoadingManager loadingManager] for the loader to use. Default is [page:LoadingManager THREE.DefaultLoadingManager].
+		</div>
+		<div>
+		Creates a new [name].
+		</div>
+
+		<h2>Properties</h2>
+
+		<h3>[page:Boolean littleEndian]</h3>
+		<div>
+		Default value is true.
+		</div>
+
+		<h2>Methods</h2>
+
+		<h3>[method:null load]( [page:String url], [page:Function onLoad], [page:Function onProgress], [page:Function onError] )</h3>
+		<div>
+		[page:String url] — required<br />
+		[page:Function onLoad] — Will be called when load completes. The argument will be the loaded [page:Object3D].<br />
+		[page:Function onProgress] — Will be called while load progresses. The argument will be the XmlHttpRequest instance, that contain .[page:Integer total] and .[page:Integer loaded] bytes.<br />
+		[page:Function onError] — Will be called when load errors.<br />
+		</div>
+		<div>
+		Begin loading from url and call onLoad with the parsed response content.
+		</div>
+
+		<h3>[method:Object3D parse]( [page:Arraybuffer data],[page:String url] )</h3>
+		<div>
+		[page:Arraybuffer data] — The binary structure to parse.
+		</div>
+		<div>
+		[page:String url] — The file name or file url.
+		</div>
+		<div>
+		Parse an <em>pcd</em> binary structure and return an [page:Object3D].<br /> 
+		The object is converted to [page:Points] with a [page:BufferGeometry] and a [page:PointsMaterial].
+		</div>
+
+		<h2>Example</h2>
+
+		<code>
+
+		// instantiate a loader
+		var loader = new THREE.PCDLoader();
+		
+		// load a resource
+		loader.load( 
+			// resource URL
+			'pointcloud.pcd' , 
+			// Function when resource is loaded
+			function ( mesh ) {
+				scene.add( mesh );
+			}
+		);
+		</code>
+
+		[example:webgl_loader_pcd]
+
+
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/examples/js/loaders/PCDLoader.js examples/js/loaders/PCDLoader.js]
+	</body>
+</html>

+ 0 - 5
docs/api/loaders/XHRLoader.html

@@ -32,11 +32,6 @@
 		A [page:Cache cache] instance that hold the response from each request made through this loader, so each file is requested once.
 		A [page:Cache cache] instance that hold the response from each request made through this loader, so each file is requested once.
 		</div>
 		</div>
 
 
-		<h3>[property:String crossOrigin]</h3>
-		<div>
-		The crossOrigin string to implement CORS for loading the url from a different domain that allows CORS.
-		</div>
-
 		<h3>[property:String responseType]</h3>
 		<h3>[property:String responseType]</h3>
 		<div>
 		<div>
 		Can be set to change the response type.
 		Can be set to change the response type.

+ 62 - 62
docs/api/materials/LineBasicMaterial.html

@@ -1,63 +1,63 @@
-<!DOCTYPE html>
-<html lang="en">
-	<head>
+<!DOCTYPE html>
+<html lang="en">
+	<head>
 		<meta charset="utf-8" />
 		<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>
-		[page:Material] &rarr;
-
-		<h1>[name]</h1>
-
-		<div class="desc">A material for drawing wireframe-style geometries.</div>
-
-
-		<h2>Constructor</h2>
-
-
-		<h3>[name]( [page:Object parameters] )</h3>
-
-		<div>parameters is an object with one or more properties defining the material's appearance.</div>
-		<div>
-		color — Line color in hexadecimal. Default is 0xffffff.<br />
-		linewidth — Line thickness. Default is 1.<br />
-		linecap — Define appearance of line ends. Default is 'round'.<br />
-		linejoin — Define appearance of line joints. Default is 'round'.<br />
-		vertexColors — Define how the vertices gets colored. Default is THREE.NoColors.<br />
-		fog — Define whether the material color is affected by global fog settings. Default is false.
-		</div>
-
-		<h2>Properties</h2>
-
-		<h3>[property:Integer color]</h3>
-		<div>Sets the color of the line. Default is 0xffffff.</div>
-
-		<h3>[property:Float linewidth]</h3>
-		<div>Controls line thickness. Default is 1.</div>
-		<div>Due to limitations in the <a href="https://code.google.com/p/angleproject/" target="_blank">ANGLE layer</a>, on Windows platforms linewidth will always be 1 regardless of the set value.</div>
-
-		<h3>[property:String linecap]</h3>
-		<div>Define appearance of line ends. Possible values are "butt", "round" and "square". Default is 'round'.</div>
-		<div>This setting might not have any effect when used with certain renderers. For example, it is ignored with the [page:WebGLRenderer WebGL] renderer, but does work with the [page:CanvasRenderer Canvas] renderer.</div>
-
-		<h3>[property:String linejoin]</h3>
-		<div>Define appearance of line joints. Possible values are "round", "bevel" and "miter". Default is 'round'.</div>
-		<div>This setting might not have any effect when used with certain renderers. For example, it is ignored with the [page:WebGLRenderer WebGL] renderer, but does work with the [page:CanvasRenderer Canvas] renderer.</div>
-
-		<h3>[property:Integer vertexColors]</h3>
-		<div>Define how the vertices gets colored. Possible values are THREE.NoColors, THREE.FaceColors and THREE.VertexColors. Default is THREE.NoColors.</div>
-		<div>This setting might not have any effect when used with certain renderers.</div>
-
-		<h3>[property:Boolean fog]</h3>
-		<div>Define whether the material color is affected by global fog settings.</div>
-		<div>This setting might not have any effect when used with certain renderers. For example, it is ignored with the [page:CanvasRenderer Canvas] renderer, but does work with the [page:WebGLRenderer WebGL] renderer.</div>
-
-		
-		<h2>Source</h2>
-
-		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
-	</body>
-</html>
+		<base href="../../" />
+		<script src="list.js"></script>
+		<script src="page.js"></script>
+		<link type="text/css" rel="stylesheet" href="page.css" />
+	</head>
+	<body>
+		[page:Material] &rarr;
+
+		<h1>[name]</h1>
+
+		<div class="desc">A material for drawing wireframe-style geometries.</div>
+
+
+		<h2>Constructor</h2>
+
+
+		<h3>[name]( [page:Object parameters] )</h3>
+
+		<div>parameters is an object with one or more properties defining the material's appearance.</div>
+		<div>
+		color — Line color in hexadecimal. Default is 0xffffff.<br />
+		linewidth — Line thickness. Default is 1.<br />
+		linecap — Define appearance of line ends. Default is 'round'.<br />
+		linejoin — Define appearance of line joints. Default is 'round'.<br />
+		vertexColors — Define how the vertices gets colored. Default is THREE.NoColors.<br />
+		fog — Define whether the material color is affected by global fog settings. Default is false.
+		</div>
+
+		<h2>Properties</h2>
+
+		<h3>[property:Integer color]</h3>
+		<div>Sets the color of the line. Default is 0xffffff.</div>
+
+		<h3>[property:Float linewidth]</h3>
+		<div>Controls line thickness. Default is 1.</div>
+		<div>Due to limitations in the <a href="https://code.google.com/p/angleproject/" target="_blank">ANGLE layer</a>, with the [page:WebGLRenderer WebGL] renderer on Windows platforms linewidth will always be 1 regardless of the set value.</div>
+
+		<h3>[property:String linecap]</h3>
+		<div>Define appearance of line ends. Possible values are "butt", "round" and "square". Default is 'round'.</div>
+		<div>This setting might not have any effect when used with certain renderers. For example, it is ignored with the [page:WebGLRenderer WebGL] renderer, but does work with the [page:CanvasRenderer Canvas] renderer.</div>
+
+		<h3>[property:String linejoin]</h3>
+		<div>Define appearance of line joints. Possible values are "round", "bevel" and "miter". Default is 'round'.</div>
+		<div>This setting might not have any effect when used with certain renderers. For example, it is ignored with the [page:WebGLRenderer WebGL] renderer, but does work with the [page:CanvasRenderer Canvas] renderer.</div>
+
+		<h3>[property:Integer vertexColors]</h3>
+		<div>Define how the vertices gets colored. Possible values are THREE.NoColors, THREE.FaceColors and THREE.VertexColors. Default is THREE.NoColors.</div>
+		<div>This setting might not have any effect when used with certain renderers.</div>
+
+		<h3>[property:Boolean fog]</h3>
+		<div>Define whether the material color is affected by global fog settings.</div>
+		<div>This setting might not have any effect when used with certain renderers. For example, it is ignored with the [page:CanvasRenderer Canvas] renderer, but does work with the [page:WebGLRenderer WebGL] renderer.</div>
+
+		
+		<h2>Source</h2>
+
+		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
+	</body>
+</html>

+ 16 - 1
docs/api/materials/MeshLambertMaterial.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="en">
 <html lang="en">
 	<head>
 	<head>
-		<meta charset="utf-8" />
+		<meta charset="utf-8" />
 		<base href="../../" />
 		<base href="../../" />
 		<script src="list.js"></script>
 		<script src="list.js"></script>
 		<script src="page.js"></script>
 		<script src="page.js"></script>
@@ -26,6 +26,9 @@
 		<div>
 		<div>
 		color — Line color in hexadecimal. Default is 0xffffff.<br />
 		color — Line color in hexadecimal. Default is 0xffffff.<br />
 		map — Sets the texture map. Default is null <br />
 		map — Sets the texture map. Default is null <br />
+		lightMap — Set light map. Default is null.<br />
+		aoMap — Set ao map. Default is null.<br />
+		emissiveMap — Set emissive map. Default is null.<br />
 		specularMap — Set specular map. Default is null.<br />
 		specularMap — Set specular map. Default is null.<br />
 		alphaMap — Set alpha map. Default is null.<br />
 		alphaMap — Set alpha map. Default is null.<br />
 		envMap — Set env map. Default is null.<br />
 		envMap — Set env map. Default is null.<br />
@@ -53,9 +56,21 @@
 		Emissive (light) color of the material, essentially a solid color unaffected by other lighting. Default is black.<br />
 		Emissive (light) color of the material, essentially a solid color unaffected by other lighting. Default is black.<br />
 		</div>
 		</div>
 
 
+		<h3>[property:Float emissiveIntensity]</h3>
+		<div>Intensity of the emissive light. Modulates the emissive color. Default is 1.</div>
+
 		<h3>[property:Texture map]</h3>
 		<h3>[property:Texture map]</h3>
 		<div>Set color texture map. Default is null.</div>
 		<div>Set color texture map. Default is null.</div>
 
 
+		<h3>[property:Texture lightMap]</h3>
+		<div>Set light map. Default is null. The lightMap requires a second set of UVs.</div>
+
+		<h3>[property:Texture aoMap]</h3>
+		<div>Set ambient occlusion map. Default is null. The aoMap requires a second set of UVs.</div>
+
+		<h3>[property:Texture emissiveMap]</h3>
+		<div>Set emisssive (glow) map. Default is null. The emissive map color is modulated by the emissive color and the emissive intensity. If you have an emissive map, be sure to set the emissive color to something other than black.</div>
+
 		<h3>[property:Texture specularMap]</h3>
 		<h3>[property:Texture specularMap]</h3>
 		<div>Since this material does not have a specular component, the specular value affects only how much of the environment map affects the surface. Default is null.</div>
 		<div>Since this material does not have a specular component, the specular value affects only how much of the environment map affects the surface. Default is null.</div>
 
 

+ 24 - 9
docs/api/materials/MeshPhongMaterial.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="en">
 <html lang="en">
 	<head>
 	<head>
-		<meta charset="utf-8" />
+		<meta charset="utf-8" />
 		<base href="../../" />
 		<base href="../../" />
 		<script src="list.js"></script>
 		<script src="list.js"></script>
 		<script src="page.js"></script>
 		<script src="page.js"></script>
@@ -31,6 +31,9 @@
 		emissiveMap — Set emissive map. Default is null.<br />
 		emissiveMap — Set emissive map. Default is null.<br />
 		specularMap — Set specular map. Default is null.<br />
 		specularMap — Set specular map. Default is null.<br />
 		alphaMap — Set alpha map. Default is null.<br />
 		alphaMap — Set alpha map. Default is null.<br />
+		displacementMap — Set displacement map. Default is null.<br />
+		displacementScale — Set displacement scale. Default is 1.<br />
+		displacementBias — Set displacement offset. Default is 0.<br />
 		envMap — Set env map. Default is null.<br />
 		envMap — Set env map. Default is null.<br />
 		fog — Define whether the material color is affected by global fog settings. Default is true.<br />
 		fog — Define whether the material color is affected by global fog settings. Default is true.<br />
 		shading — Define shading type. Default is THREE.SmoothShading.<br />
 		shading — Define shading type. Default is THREE.SmoothShading.<br />
@@ -62,6 +65,9 @@
 		Emissive (light) color of the material, essentially a solid color unaffected by other lighting. Default is black.<br />
 		Emissive (light) color of the material, essentially a solid color unaffected by other lighting. Default is black.<br />
 		</div>
 		</div>
 
 
+		<h3>[property:Float emissiveIntensity]</h3>
+		<div>Intensity of the emissive light. Modulates the emissive color. Default is 1.</div>
+
 		<h3>[property:Color specular]</h3>
 		<h3>[property:Color specular]</h3>
 		<div>
 		<div>
 		Specular color of the material, i.e., how shiny the material is and the color of its shine. Setting this the same color as the diffuse value (times some intensity) makes the material more metallic-looking; setting this to some gray makes the material look more plastic. Default is dark gray.<br />
 		Specular color of the material, i.e., how shiny the material is and the color of its shine. Setting this the same color as the diffuse value (times some intensity) makes the material more metallic-looking; setting this to some gray makes the material look more plastic. Default is dark gray.<br />
@@ -70,13 +76,6 @@
 		<h3>[property:Float shininess]</h3>
 		<h3>[property:Float shininess]</h3>
 		<div>How shiny the specular highlight is; a higher value gives a sharper highlight. Default is *30*.</div>
 		<div>How shiny the specular highlight is; a higher value gives a sharper highlight. Default is *30*.</div>
 
 
-		<h3>[property:boolean metal]</h3>
-		<div>
-			If set to true the shader multiplies the specular highlight by the underlying color of the object, making
-			it appear to be more metal-like and darker. If set to false the specular highlight is added ontop of the
-			underlying colors.
-		</div>
-
 		<h3>[property:Texture map]</h3>
 		<h3>[property:Texture map]</h3>
 		<div>Set color texture map. Default is null. The texture map color is modulated by the diffuse color.</div>
 		<div>Set color texture map. Default is null. The texture map color is modulated by the diffuse color.</div>
 
 
@@ -87,7 +86,7 @@
 		<div>Set ambient occlusion map. Default is null. The aoMap requires a second set of UVs.</div>
 		<div>Set ambient occlusion map. Default is null. The aoMap requires a second set of UVs.</div>
 
 
 		<h3>[property:Texture emissiveMap]</h3>
 		<h3>[property:Texture emissiveMap]</h3>
-		<div>Set emisssive (glow) map. Default is null. The emissive map color is modulated by the emissive color. If you have an emissive map, be sure to set the emissive color to something other than black.</div>
+		<div>Set emisssive (glow) map. Default is null. The emissive map color is modulated by the emissive color and the emissive intensity. If you have an emissive map, be sure to set the emissive color to something other than black.</div>
 
 
 		<h3>[property:Texture bumpMap]</h3>
 		<h3>[property:Texture bumpMap]</h3>
 		<div>
 		<div>
@@ -119,6 +118,22 @@
 		<div>The alpha map is a grayscale texture that controls the opacity across the surface (black: fully transparent; white: fully opaque). Default is null.</div>
 		<div>The alpha map is a grayscale texture that controls the opacity across the surface (black: fully transparent; white: fully opaque). Default is null.</div>
 		<div>Only the color of the texture is used, ignoring the alpha channel if one exists. For RGB and RGBA textures, the [page:WebGLRenderer WebGL] renderer will use the green channel when sampling this texture due to the extra bit of precision provided for green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and luminance/alpha textures will also still work as expected.</div>
 		<div>Only the color of the texture is used, ignoring the alpha channel if one exists. For RGB and RGBA textures, the [page:WebGLRenderer WebGL] renderer will use the green channel when sampling this texture due to the extra bit of precision provided for green in DXT-compressed and uncompressed RGB 565 formats. Luminance-only and luminance/alpha textures will also still work as expected.</div>
 
 
+		<h3>[property:Texture displacementMap]</h3>
+		<div>
+			The displacement map affects the position of the mesh's vertices. Unlike other maps which only affect the light and shade of the material the displaced vertices can cast shadows, block other objects, and otherwise act as real geometry. 
+			The displacement texture is an image where the value of each pixel (white being the highest) is mapped against, and repositions, the vertices of the mesh.
+		</div>
+		
+		<h3>[property:Float displacementScale]</h3>
+		<div>
+			How much the displacement map affects the mesh (where black is no displacement, and white is maximum displacement). Without a displacement map set, this value is not applied. Default is 1.
+		</div>
+		
+		<h3>[property:Float displacementBias]</h3>
+		<div>
+			The offset of the displacement map's values on the mesh's vertices. Without a displacement map set, this value is not applied. Default is 0.
+		</div>
+		
 		<h3>[property:TextureCube envMap]</h3>
 		<h3>[property:TextureCube envMap]</h3>
 		<div>Set env map. Default is null.</div>
 		<div>Set env map. Default is null.</div>
 
 

+ 2 - 2
docs/api/materials/MeshFaceMaterial.html → docs/api/materials/MultiMaterial.html

@@ -11,7 +11,7 @@
 		<h1>[name]</h1>
 		<h1>[name]</h1>
 
 
 		<div class="desc">
 		<div class="desc">
-		A Material to define multiple materials for the same geometry. 
+		A Material to define multiple materials for the same geometry.
 		The geometry decides which material is used for which faces by the [page:Face3 faces materialindex].
 		The geometry decides which material is used for which faces by the [page:Face3 faces materialindex].
 		The materialindex corresponds with the index of the material in the materials array.
 		The materialindex corresponds with the index of the material in the materials array.
 		</div>
 		</div>
@@ -25,7 +25,7 @@
 		materials -- The materials for the geometry.
 		materials -- The materials for the geometry.
 		</div>
 		</div>
 		<div>
 		<div>
-		Creates a MeshFaceMaterial with the correct materials.
+		Creates a MultiMaterial with the correct materials.
 		</div>
 		</div>
 
 
 
 

+ 1 - 1
docs/api/materials/ShaderMaterial.html

@@ -46,7 +46,7 @@
 		<h3>Vertex shaders and fragment shaders</h3>
 		<h3>Vertex shaders and fragment shaders</h3>
 		<p>You can specify two different types of shaders for each material:
 		<p>You can specify two different types of shaders for each material:
 		<ul>
 		<ul>
-			<li>The *vertex shader* runs first; it recieves *attributes*, calculates/manipulates the position of each individual vertex, and passes additional data (*varying*s) to the fragment shader.</li>
+			<li>The *vertex shader* runs first; it receives *attributes*, calculates/manipulates the position of each individual vertex, and passes additional data (*varying*s) to the fragment shader.</li>
 			<li>The *fragment shader* runs second; it sets the color of each individual "fragment" (pixel) rendered to the screen.</li>
 			<li>The *fragment shader* runs second; it sets the color of each individual "fragment" (pixel) rendered to the screen.</li>
 		</ul>
 		</ul>
 		</p>
 		</p>

+ 10 - 10
docs/api/math/Box2.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="en">
 <html lang="en">
 	<head>
 	<head>
-		<meta charset="utf-8" />
+		<meta charset="utf-8" />
 		<base href="../../" />
 		<base href="../../" />
 		<script src="list.js"></script>
 		<script src="list.js"></script>
 		<script src="page.js"></script>
 		<script src="page.js"></script>
@@ -22,7 +22,7 @@
 		max -- Upper (x, y) boundary of the box.
 		max -- Upper (x, y) boundary of the box.
 		</div>
 		</div>
 		<div>
 		<div>
-		Creates a box bounded by min and max. 
+		Creates a box bounded by min and max.
 		</div>
 		</div>
 
 
 
 
@@ -33,12 +33,12 @@
 		<h3>[property:Vector2 min]</h3>
 		<h3>[property:Vector2 min]</h3>
 		<div>
 		<div>
 		Lower (x, y) boundary of this box.
 		Lower (x, y) boundary of this box.
-		</div> 
+		</div>
 
 
 		<h3>[property:Vector2 max]</h3>
 		<h3>[property:Vector2 max]</h3>
 		<div>
 		<div>
 		Upper (x, y) boundary of this box.
 		Upper (x, y) boundary of this box.
-		</div> 
+		</div>
 
 
 		<h2>Methods</h2>
 		<h2>Methods</h2>
 
 
@@ -70,7 +70,7 @@
 		Clamps *point* within the bounds of this box.
 		Clamps *point* within the bounds of this box.
 		</div>
 		</div>
 
 
-		<h3>[method:Boolean isIntersectionBox]([page:Box2 box])</h3>
+		<h3>[method:Boolean intersectsBox]([page:Box2 box])</h3>
 		<div>
 		<div>
 		box -- Box to check for intersection against.
 		box -- Box to check for intersection against.
 		</div>
 		</div>
@@ -99,7 +99,7 @@
 		box -- Box that will be unioned with this box.
 		box -- Box that will be unioned with this box.
 		</div>
 		</div>
 		<div>
 		<div>
-		Unions this box with *box* setting the upper bound of this box to the greater of the 
+		Unions this box with *box* setting the upper bound of this box to the greater of the
 		two boxes' upper bounds and the lower bound of this box to the lesser of the two boxes'
 		two boxes' upper bounds and the lower bound of this box to the lesser of the two boxes'
 		lower bounds.
 		lower bounds.
 		</div>
 		</div>
@@ -108,7 +108,7 @@
 		<div>
 		<div>
 		point -- [page:Vector2]<br/>
 		point -- [page:Vector2]<br/>
 		optionalTarget -- [page:Vector2]<br/>
 		optionalTarget -- [page:Vector2]<br/>
-		
+
 		</div>
 		</div>
 		<div>
 		<div>
 		Returns a point as a proportion of this box's width and height.
 		Returns a point as a proportion of this box's width and height.
@@ -139,7 +139,7 @@
 		</div>
 		</div>
 		<div>
 		<div>
 		Returns true if this box includes the entirety of *box*. If this and *box* overlap exactly,</br>
 		Returns true if this box includes the entirety of *box*. If this and *box* overlap exactly,</br>
-		this function also returns true. 
+		this function also returns true.
 		</div>
 		</div>
 
 
 		<h3>[method:Box2 translate]([page:Vector2 offset]) [page:Box2 this]</h3>
 		<h3>[method:Box2 translate]([page:Vector2 offset]) [page:Box2 this]</h3>
@@ -177,7 +177,7 @@
 		</div>
 		</div>
 		<div>
 		<div>
 		Expands this box equilaterally by *vector*. The width of this box will be
 		Expands this box equilaterally by *vector*. The width of this box will be
-		expanded by the x component of *vector* in both directions. The height of 
+		expanded by the x component of *vector* in both directions. The height of
 		this box will be expanded by the y component of *vector* in both directions.
 		this box will be expanded by the y component of *vector* in both directions.
 		</div>
 		</div>
 
 
@@ -222,7 +222,7 @@
 		<h3>[method:Box2 setFromCenterAndSize]([page:Vector2 center], [page:Vector2 size]) [page:Box2 this]</h3>
 		<h3>[method:Box2 setFromCenterAndSize]([page:Vector2 center], [page:Vector2 size]) [page:Box2 this]</h3>
 		<div>
 		<div>
 		center -- Desired center position of the box. <br />
 		center -- Desired center position of the box. <br />
-		size -- Desired x and y dimensions of the box. 
+		size -- Desired x and y dimensions of the box.
 		</div>
 		</div>
 		<div>
 		<div>
 		Centers this box on *center* and sets this box's width and height to the values specified
 		Centers this box on *center* and sets this box's width and height to the values specified

+ 15 - 15
docs/api/math/Box3.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="en">
 <html lang="en">
 	<head>
 	<head>
-		<meta charset="utf-8" />
+		<meta charset="utf-8" />
 		<base href="../../" />
 		<base href="../../" />
 		<script src="list.js"></script>
 		<script src="list.js"></script>
 		<script src="page.js"></script>
 		<script src="page.js"></script>
@@ -22,7 +22,7 @@
 		max -- Upper (x, y, z) boundary of the box.
 		max -- Upper (x, y, z) boundary of the box.
 		</div>
 		</div>
 		<div>
 		<div>
-		Creates a box bounded by min and max. 
+		Creates a box bounded by min and max.
 		</div>
 		</div>
 
 
 		<h2>Properties</h2>
 		<h2>Properties</h2>
@@ -37,7 +37,7 @@
 		<h3>[property:Vector3 max]</h3>
 		<h3>[property:Vector3 max]</h3>
 		<div>
 		<div>
 		Upper (x, y, z) boundary of this box.
 		Upper (x, y, z) boundary of this box.
-		</div> 
+		</div>
 
 
 		<h2>Methods</h2>
 		<h2>Methods</h2>
 
 
@@ -51,7 +51,7 @@
 		<div>
 		<div>
 		Sets the lower and upper (x, y, z) boundaries of this box.
 		Sets the lower and upper (x, y, z) boundaries of this box.
 		</div>
 		</div>
-		
+
 		<h3>[method:Box3 applyMatrix4]([page:Matrix4 matrix]) [page:Box3 this]</h3>
 		<h3>[method:Box3 applyMatrix4]([page:Matrix4 matrix]) [page:Box3 this]</h3>
 		<div>
 		<div>
 		matrix -- The [page:Matrix4] to apply
 		matrix -- The [page:Matrix4] to apply
@@ -69,7 +69,7 @@
 		Clamps *point* within the bounds of this box.
 		Clamps *point* within the bounds of this box.
 		</div>
 		</div>
 
 
-		<h3>[method:Boolean isIntersectionBox]([page:Box3 box])</h3>
+		<h3>[method:Boolean intersectsBox]([page:Box3 box])</h3>
 		<div>
 		<div>
 		box -- Box to check for intersection against.
 		box -- Box to check for intersection against.
 		</div>
 		</div>
@@ -84,7 +84,7 @@
 		<div>
 		<div>
 		Sets the upper and lower bounds of this box to include all of the points in *points*.
 		Sets the upper and lower bounds of this box to include all of the points in *points*.
 		</div>
 		</div>
-		
+
 		<h3>[method:Box3 setFromObject]([page:Object3D object]) [page:Box3 this]</h3>
 		<h3>[method:Box3 setFromObject]([page:Object3D object]) [page:Box3 this]</h3>
 		<div>
 		<div>
 		object -- [page:Object3D] to compute the bounding box for.
 		object -- [page:Object3D] to compute the bounding box for.
@@ -93,8 +93,8 @@
 		Computes the world-axis-aligned bounding box of an object (including its children),
 		Computes the world-axis-aligned bounding box of an object (including its children),
 		accounting for both the object's, and childrens', world transforms
 		accounting for both the object's, and childrens', world transforms
 		</div>
 		</div>
-		
-		
+
+
 
 
 		<h3>[method:Vector3 size]([page:Vector3 optionalTarget])</h3>
 		<h3>[method:Vector3 size]([page:Vector3 optionalTarget])</h3>
 		<div>
 		<div>
@@ -109,7 +109,7 @@
 		box -- Box that will be unioned with this box.
 		box -- Box that will be unioned with this box.
 		</div>
 		</div>
 		<div>
 		<div>
-		Unions this box with *box* setting the upper bound of this box to the greater of the 
+		Unions this box with *box* setting the upper bound of this box to the greater of the
 		two boxes' upper bounds and the lower bound of this box to the lesser of the two boxes'
 		two boxes' upper bounds and the lower bound of this box to the lesser of the two boxes'
 		lower bounds.
 		lower bounds.
 		</div>
 		</div>
@@ -139,7 +139,7 @@
 		</div>
 		</div>
 		<div>
 		<div>
 		Returns true if this box includes the entirety of *box*. If this and *box* overlap exactly,</br>
 		Returns true if this box includes the entirety of *box*. If this and *box* overlap exactly,</br>
-		this function also returns true. 
+		this function also returns true.
 		</div>
 		</div>
 
 
 		<h3>[method:Boolean containsPoint]([page:Vector3 point])</h3>
 		<h3>[method:Boolean containsPoint]([page:Vector3 point])</h3>
@@ -149,7 +149,7 @@
 		<div>
 		<div>
 		Returns true if the specified point lies within the boundaries of this box.
 		Returns true if the specified point lies within the boundaries of this box.
 		</div>
 		</div>
-		
+
 		<h3>[method:Box3 translate]([page:Vector3 offset]) [page:Box3 this]</h3>
 		<h3>[method:Box3 translate]([page:Vector3 offset]) [page:Box3 this]</h3>
 		<div>
 		<div>
 		offset -- Direction and distance of offset.
 		offset -- Direction and distance of offset.
@@ -186,7 +186,7 @@
 		<div>
 		<div>
 		Expands the boundaries of this box to include *point*.
 		Expands the boundaries of this box to include *point*.
 		</div>
 		</div>
-		
+
 		<h3>[method:Box3 expandByScalar]([page:float scalar]) [page:Box3 this]</h3>
 		<h3>[method:Box3 expandByScalar]([page:float scalar]) [page:Box3 this]</h3>
 		<div>
 		<div>
 		scalar -- Distance to expand.
 		scalar -- Distance to expand.
@@ -195,14 +195,14 @@
 		Expands each dimension of the box by *scalar*. If negative, the dimensions of the box <br/>
 		Expands each dimension of the box by *scalar*. If negative, the dimensions of the box <br/>
 		will be contracted.
 		will be contracted.
 		</div>
 		</div>
-		
+
 		<h3>[method:Box3 expandByVector]([page:Vector3 vector]) [page:Box3 this]</h3>
 		<h3>[method:Box3 expandByVector]([page:Vector3 vector]) [page:Box3 this]</h3>
 		<div>
 		<div>
 		vector -- Amount to expand this box in each dimension.
 		vector -- Amount to expand this box in each dimension.
 		</div>
 		</div>
 		<div>
 		<div>
 		Expands this box equilaterally by *vector*. The width of this box will be
 		Expands this box equilaterally by *vector*. The width of this box will be
-		expanded by the x component of *vector* in both directions. The height of 
+		expanded by the x component of *vector* in both directions. The height of
 		this box will be expanded by the y component of *vector* in both directions.
 		this box will be expanded by the y component of *vector* in both directions.
 		The depth of this box will be expanded by the z component of *vector* in
 		The depth of this box will be expanded by the z component of *vector* in
 		both directions.
 		both directions.
@@ -249,7 +249,7 @@
 		<h3>[method:Box3 setFromCenterAndSize]([page:Vector3 center], [page:Vector3 size]) [page:Box3 this]</h3>
 		<h3>[method:Box3 setFromCenterAndSize]([page:Vector3 center], [page:Vector3 size]) [page:Box3 this]</h3>
 		<div>
 		<div>
 		center -- Desired center position of the box. <br />
 		center -- Desired center position of the box. <br />
-		size -- Desired x and y dimensions of the box. 
+		size -- Desired x and y dimensions of the box.
 		</div>
 		</div>
 		<div>
 		<div>
 		Centers this box on *center* and sets this box's width and height to the values specified
 		Centers this box on *center* and sets this box's width and height to the values specified

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

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="en">
 <html lang="en">
 	<head>
 	<head>
-		<meta charset="utf-8" />
+		<meta charset="utf-8" />
 		<base href="../../" />
 		<base href="../../" />
 		<script src="list.js"></script>
 		<script src="list.js"></script>
 		<script src="page.js"></script>
 		<script src="page.js"></script>

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

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="en">
 <html lang="en">
 	<head>
 	<head>
-		<meta charset="utf-8" />
+		<meta charset="utf-8" />
 		<base href="../../" />
 		<base href="../../" />
 		<script src="list.js"></script>
 		<script src="list.js"></script>
 		<script src="page.js"></script>
 		<script src="page.js"></script>

+ 3 - 3
docs/api/math/Line3.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="en">
 <html lang="en">
 	<head>
 	<head>
-		<meta charset="utf-8" />
+		<meta charset="utf-8" />
 		<base href="../../" />
 		<base href="../../" />
 		<script src="list.js"></script>
 		<script src="list.js"></script>
 		<script src="page.js"></script>
 		<script src="page.js"></script>
@@ -72,7 +72,7 @@
 		</div>
 		</div>
 		Returns true if both line's start and end points are equal.
 		Returns true if both line's start and end points are equal.
 		</div>
 		</div>
-		
+
 		<h3>[method:Float distanceSq]()</h3>
 		<h3>[method:Float distanceSq]()</h3>
 		<div>
 		<div>
 		Returns the line segment's length squared.
 		Returns the line segment's length squared.
@@ -94,7 +94,7 @@
 		<div>
 		<div>
 		Return a vector at a certain position along the line. When t = 0, it returns the start vector, and when t=1 it returns the end vector.
 		Return a vector at a certain position along the line. When t = 0, it returns the start vector, and when t=1 it returns the end vector.
 		</div>
 		</div>
-		
+
 		<h3>[method:Vector3 center]([page:Vector3 optionalTarget])</h3>
 		<h3>[method:Vector3 center]([page:Vector3 optionalTarget])</h3>
 		<div>
 		<div>
 		optionalTarget -- [page:Vector3] Optional target to set the result.
 		optionalTarget -- [page:Vector3] Optional target to set the result.

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

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="en">
 <html lang="en">
 	<head>
 	<head>
-		<meta charset="utf-8" />
+		<meta charset="utf-8" />
 		<base href="../../" />
 		<base href="../../" />
 		<script src="list.js"></script>
 		<script src="list.js"></script>
 		<script src="page.js"></script>
 		<script src="page.js"></script>

+ 4 - 4
docs/api/math/Matrix3.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="en">
 <html lang="en">
 	<head>
 	<head>
-		<meta charset="utf-8" />
+		<meta charset="utf-8" />
 		<base href="../../" />
 		<base href="../../" />
 		<script src="list.js"></script>
 		<script src="list.js"></script>
 		<script src="page.js"></script>
 		<script src="page.js"></script>
@@ -92,10 +92,10 @@
 		Sets this matrix as the normal matrix (upper left 3x3)of the passed [page:Matrix4 matrix4]. The normal matrix is the inverse transpose of the matrix *m*.
 		Sets this matrix as the normal matrix (upper left 3x3)of the passed [page:Matrix4 matrix4]. The normal matrix is the inverse transpose of the matrix *m*.
 		</div>
 		</div>
 
 
-		<h3>[method:Matrix3 getInverse]([page:Matrix4 m], [page:Boolean throwOnInvertible]) [page:Matrix3 this]</h3>
+		<h3>[method:Matrix3 getInverse]([page:Matrix4 m], [page:Boolean throwOnDegenerate]) [page:Matrix3 this]</h3>
 		<div>
 		<div>
 		m -- [page:Matrix4]<br />
 		m -- [page:Matrix4]<br />
-		throwOnInvertible -- [Page:Boolean] If true, throw an error if the matrix is invertible.
+		throwOnDegenerate -- [Page:Boolean] If true, throw an error if the matrix is degenerate (not invertible).
 		</div>
 		</div>
 		<div>
 		<div>
 		Set this matrix to the inverse of the passed matrix.
 		Set this matrix to the inverse of the passed matrix.
@@ -117,7 +117,7 @@
 		<h3>[method:Matrix3 identity]() [page:Matrix3 this]</h3>
 		<h3>[method:Matrix3 identity]() [page:Matrix3 this]</h3>
 		<div>
 		<div>
 		Resets this matrix to identity.<br/><br/>
 		Resets this matrix to identity.<br/><br/>
-		
+
 		1, 0, 0<br/>
 		1, 0, 0<br/>
 		0, 1, 0<br/>
 		0, 1, 0<br/>
 		0, 0, 1<br/>
 		0, 0, 1<br/>

+ 3 - 3
docs/api/math/Matrix4.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="en">
 <html lang="en">
 	<head>
 	<head>
-		<meta charset="utf-8" />
+		<meta charset="utf-8" />
 		<base href="../../" />
 		<base href="../../" />
 		<script src="list.js"></script>
 		<script src="list.js"></script>
 		<script src="page.js"></script>
 		<script src="page.js"></script>
@@ -72,12 +72,12 @@
 		Copies the translation component of the supplied matrix *m* into this matrix translation component.
 		Copies the translation component of the supplied matrix *m* into this matrix translation component.
 		</div>
 		</div>
 
 
-		<h3>[method:Matrix4 makeBasis]( [page:Vector3 xAxis], [page:Vector3 zAxis], [page:Vector3 zAxis] ) [page:Matrix4 this]</h3>
+		<h3>[method:Matrix4 makeBasis]( [page:Vector3 xAxis], [page:Vector3 yAxis], [page:Vector3 zAxis] ) [page:Matrix4 this]</h3>
 		<div>
 		<div>
 		Creates the basis matrix consisting of the three provided axis vectors.  Returns the current matrix.
 		Creates the basis matrix consisting of the three provided axis vectors.  Returns the current matrix.
 		</div>
 		</div>
 
 
-		<h3>[method:Matrix4 extractBasis]( [page:Vector3 xAxis], [page:Vector3 zAxis], [page:Vector3 zAxis] ) [page:Matrix4 this]</h3>
+		<h3>[method:Matrix4 extractBasis]( [page:Vector3 xAxis], [page:Vector3 yAxis], [page:Vector3 zAxis] ) [page:Matrix4 this]</h3>
 		<div>
 		<div>
 		Extracts basis of into the three axis vectors provided.  Returns the current matrix.
 		Extracts basis of into the three axis vectors provided.  Returns the current matrix.
 		</div>
 		</div>

+ 5 - 5
docs/api/math/Plane.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="en">
 <html lang="en">
 	<head>
 	<head>
-		<meta charset="utf-8" />
+		<meta charset="utf-8" />
 		<base href="../../" />
 		<base href="../../" />
 		<script src="list.js"></script>
 		<script src="list.js"></script>
 		<script src="page.js"></script>
 		<script src="page.js"></script>
@@ -28,7 +28,7 @@
 		<h3>[property:Vector3 normal]</h3>
 		<h3>[property:Vector3 normal]</h3>
 
 
 		<h3>[property:Float constant]</h3>
 		<h3>[property:Float constant]</h3>
-		
+
 		<h2>Methods</h2>
 		<h2>Methods</h2>
 
 
 
 
@@ -62,9 +62,9 @@
 		</div>
 		</div>
 		<div>
 		<div>
 		Apply a Matrix4 to the plane. The second parameter is optional.
 		Apply a Matrix4 to the plane. The second parameter is optional.
-		
+
 		<code>
 		<code>
-		var optionalNormalMatrix = new THREE.Matrix3().getNormalMatrix( matrix ) 
+		var optionalNormalMatrix = new THREE.Matrix3().getNormalMatrix( matrix )
 		</code>
 		</code>
 		</div>
 		</div>
 
 
@@ -77,7 +77,7 @@
 		Returns a vector in the same direction as the Plane's normal, but the magnitude is passed point's original distance to the plane.
 		Returns a vector in the same direction as the Plane's normal, but the magnitude is passed point's original distance to the plane.
 		</div>
 		</div>
 
 
-		<h3>[method:Boolean isIntersectionLine]([page:Line3 line])</h3>
+		<h3>[method:Boolean intersectsLine]([page:Line3 line])</h3>
 		<div>
 		<div>
 		line -- [page:Line3]
 		line -- [page:Line3]
 		</div>
 		</div>

+ 4 - 4
docs/api/math/Quaternion.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="en">
 <html lang="en">
 	<head>
 	<head>
-		<meta charset="utf-8" />
+		<meta charset="utf-8" />
 		<base href="../../" />
 		<base href="../../" />
 		<script src="list.js"></script>
 		<script src="list.js"></script>
 		<script src="page.js"></script>
 		<script src="page.js"></script>
@@ -170,8 +170,8 @@
 		// rotate a mesh towards a target quaternion
 		// rotate a mesh towards a target quaternion
 		mesh.quaternion.slerp( endQuaternion, 0.01 );
 		mesh.quaternion.slerp( endQuaternion, 0.01 );
 		</code>
 		</code>
-		
-		
+
+
 		<h2>Static Methods</h2>
 		<h2>Static Methods</h2>
 
 
 		<h3>[method:Quaternion slerp]( [page:Quaternion qStart], [page:Quaternion qEnd], [page:Quaternion qTarget], [page:Float t] )</h3>
 		<h3>[method:Quaternion slerp]( [page:Quaternion qStart], [page:Quaternion qEnd], [page:Quaternion qTarget], [page:Float t] )</h3>
@@ -195,7 +195,7 @@
 		t = ( t + 0.01 ) % 1; // constant angular momentum
 		t = ( t + 0.01 ) % 1; // constant angular momentum
 		THREE.Quaternion.slerp( startQuaternion, endQuaternion, mesh.quaternion, t );
 		THREE.Quaternion.slerp( startQuaternion, endQuaternion, mesh.quaternion, t );
 		</code>
 		</code>
-		
+
 		<!-- Note: Do not add non-static methods to the bottom of this page. Put them above the <h2>Static Methods</h2> -->
 		<!-- Note: Do not add non-static methods to the bottom of this page. Put them above the <h2>Static Methods</h2> -->
 
 
 		<h2>Source</h2>
 		<h2>Source</h2>

+ 18 - 10
docs/api/math/Ray.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="en">
 <html lang="en">
 	<head>
 	<head>
-		<meta charset="utf-8" />
+		<meta charset="utf-8" />
 		<base href="../../" />
 		<base href="../../" />
 		<script src="list.js"></script>
 		<script src="list.js"></script>
 		<script src="page.js"></script>
 		<script src="page.js"></script>
@@ -30,12 +30,12 @@
 		<h3>[property:Vector3 origin]</h3>
 		<h3>[property:Vector3 origin]</h3>
 		<div>
 		<div>
 		The origin of the [page:Ray].
 		The origin of the [page:Ray].
-		</div> 
+		</div>
 
 
 		<h3>[property:Vector3 direction]</h3>
 		<h3>[property:Vector3 direction]</h3>
 		<div>
 		<div>
 		The direction of the [page:Ray]. This must be normalized (with [page:Vector3].normalize) for the methods to operate properly.
 		The direction of the [page:Ray]. This must be normalized (with [page:Vector3].normalize) for the methods to operate properly.
-		</div> 
+		</div>
 
 
 		<h2>Methods</h2>
 		<h2>Methods</h2>
 
 
@@ -77,7 +77,7 @@
 		<div>
 		<div>
 		Copy the properties of the provided [page:Ray], then return this [page:Ray].
 		Copy the properties of the provided [page:Ray], then return this [page:Ray].
 		</div>
 		</div>
-		
+
 		<h3>.distanceSqToSegment([page:Vector3 v0], [page:Vector3 v1], [page:Vector3 optionalPointOnRay] = null, [page:Vector3 optionalPointOnSegment] = null) [page:Float]</h3>
 		<h3>.distanceSqToSegment([page:Vector3 v0], [page:Vector3 v1], [page:Vector3 optionalPointOnRay] = null, [page:Vector3 optionalPointOnSegment] = null) [page:Float]</h3>
 		<div>
 		<div>
 		v0 -- [page:Vector3] The start of the line segment.
 		v0 -- [page:Vector3] The start of the line segment.
@@ -129,7 +129,7 @@
 		<div>
 		<div>
 		Intersect this [page:Ray] with a [page:Box3], returning the intersection point or *null* if there is no intersection.
 		Intersect this [page:Ray] with a [page:Box3], returning the intersection point or *null* if there is no intersection.
 		</div>
 		</div>
-		
+
 		<h3>.intersectPlane([page:Plane plane], [page:Vector3 optionalTarget] = null) [page:Vector3]?</h3>
 		<h3>.intersectPlane([page:Plane plane], [page:Vector3 optionalTarget] = null) [page:Vector3]?</h3>
 		<div>
 		<div>
 		plane -- [page:Plane] The [page:Plane] to intersect with.<br />
 		plane -- [page:Plane] The [page:Plane] to intersect with.<br />
@@ -139,7 +139,7 @@
 		Intersect this [page:Ray] with a [page:Plane], returning the intersection point or *null* if there is no intersection.
 		Intersect this [page:Ray] with a [page:Plane], returning the intersection point or *null* if there is no intersection.
 		</div>
 		</div>
 		function ( a, b, c, backfaceCulling, optionalTarget )
 		function ( a, b, c, backfaceCulling, optionalTarget )
-		
+
 		<h3>.intersectTriangle([page:Vector3 a], [page:Vector3 b], [page:Vector3 c], [page:Boolean backfaceCulling], [page:Vector3 optionalTarget] = null) [page:Vector3]?</h3>
 		<h3>.intersectTriangle([page:Vector3 a], [page:Vector3 b], [page:Vector3 c], [page:Boolean backfaceCulling], [page:Vector3 optionalTarget] = null) [page:Vector3]?</h3>
 		<div>
 		<div>
 		a, b, c -- [page:Vector3] The [page:Vector3] points on the triangle.<br />
 		a, b, c -- [page:Vector3] The [page:Vector3] points on the triangle.<br />
@@ -149,8 +149,8 @@
 		<div>
 		<div>
 		Intersect this [page:Ray] with a triangle, returning the intersection point or *null* if there is no intersection.
 		Intersect this [page:Ray] with a triangle, returning the intersection point or *null* if there is no intersection.
 		</div>
 		</div>
-		
-		<h3>[method:Boolean isIntersectionBox]([page:Box3 box])</h3>
+
+		<h3>[method:Boolean intersectsBox]([page:Box3 box])</h3>
 		<div>
 		<div>
 		box -- [page:Box3] The [page:Box3] to intersect with.
 		box -- [page:Box3] The [page:Box3] to intersect with.
 		</div>
 		</div>
@@ -158,7 +158,7 @@
 		Return whether or not this [page:Ray] intersects with the [page:Box3].
 		Return whether or not this [page:Ray] intersects with the [page:Box3].
 		</div>
 		</div>
 
 
-		<h3>[method:Boolean isIntersectionPlane]([page:Plane plane])</h3>
+		<h3>[method:Boolean intersectsPlane]([page:Plane plane])</h3>
 		<div>
 		<div>
 		plane -- [page:Plane] The [page:Plane] to intersect with.
 		plane -- [page:Plane] The [page:Plane] to intersect with.
 		</div>
 		</div>
@@ -166,7 +166,7 @@
 		Return whether or not this [page:Ray] intersects with the [page:Plane].
 		Return whether or not this [page:Ray] intersects with the [page:Plane].
 		</div>
 		</div>
 
 
-		<h3>[method:Boolean isIntersectionSphere]([page:Sphere sphere])</h3>
+		<h3>[method:Boolean intersectsSphere]([page:Sphere sphere])</h3>
 		<div>
 		<div>
 		sphere -- [page:Sphere] The [page:Sphere] to intersect with.
 		sphere -- [page:Sphere] The [page:Sphere] to intersect with.
 		</div>
 		</div>
@@ -191,6 +191,14 @@
 		Copy the parameters to the origin and direction properties.
 		Copy the parameters to the origin and direction properties.
 		</div>
 		</div>
 
 
+		<h3>[method:Ray lookAt]([page:Vector3 v])</h3>
+		<div>
+		v -- [page:Vector3] The vector to look at.
+		</div>
+		<div>
+		Adjusts the direction of the ray to point at the vector in world coordinates.
+		</div>
+
 		<h2>Source</h2>
 		<h2>Source</h2>
 
 
 		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]
 		[link:https://github.com/mrdoob/three.js/blob/master/src/[path].js src/[path].js]

+ 6 - 6
docs/api/math/Sphere.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="en">
 <html lang="en">
 	<head>
 	<head>
-		<meta charset="utf-8" />
+		<meta charset="utf-8" />
 		<base href="../../" />
 		<base href="../../" />
 		<script src="list.js"></script>
 		<script src="list.js"></script>
 		<script src="page.js"></script>
 		<script src="page.js"></script>
@@ -21,16 +21,16 @@
 		center -- [page:Vector3] <br />
 		center -- [page:Vector3] <br />
 		radius -- [page:Float]
 		radius -- [page:Float]
 		</div>
 		</div>
-		
+
 
 
 		<h2>Properties</h2>
 		<h2>Properties</h2>
 
 
 
 
 		<h3>[property:Vector3 center]</h3>
 		<h3>[property:Vector3 center]</h3>
-		
+
 		<h3>[property:Float radius]</h3>
 		<h3>[property:Float radius]</h3>
-		
-		
+
+
 		<h2>Methods</h2>
 		<h2>Methods</h2>
 
 
 
 
@@ -84,7 +84,7 @@
 
 
 		<h3>[method:Sphere setFromPoints]([page:Array points], [page:Vector3 optionalCenter]) [page:Sphere this]</h3>
 		<h3>[method:Sphere setFromPoints]([page:Array points], [page:Vector3 optionalCenter]) [page:Sphere this]</h3>
 		<div>
 		<div>
-		points -- [page:Array] of [page:Vector3] positions.<br />  
+		points -- [page:Array] of [page:Vector3] positions.<br />
 		optionalCenter -- Optional [page:Vector3] position for the sphere's center.<br />
 		optionalCenter -- Optional [page:Vector3] position for the sphere's center.<br />
 		</div>
 		</div>
 		<div>
 		<div>

+ 5 - 5
docs/api/math/Spline.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="en">
 <html lang="en">
 	<head>
 	<head>
-		<meta charset="utf-8" />
+		<meta charset="utf-8" />
 		<base href="../../" />
 		<base href="../../" />
 		<script src="list.js"></script>
 		<script src="list.js"></script>
 		<script src="page.js"></script>
 		<script src="page.js"></script>
@@ -17,7 +17,7 @@
 
 
 
 
 		<h3>[name]( [page:Array points] )</h3>
 		<h3>[name]( [page:Array points] )</h3>
-		
+
 		<div>Initialises the spline with *points*, which are the places through which the spline will go.</div>
 		<div>Initialises the spline with *points*, which are the places through which the spline will go.</div>
 
 
 
 
@@ -40,11 +40,11 @@
 		k — point index
 		k — point index
 		</div>
 		</div>
 		<div>Return the interpolated point at *k*.</div>
 		<div>Return the interpolated point at *k*.</div>
-		
+
 		<h3>[method:Array getControlPointsArray]( )</h3>
 		<h3>[method:Array getControlPointsArray]( )</h3>
 		<div>Returns an array with triplets of x, y, z coordinates that correspond to the current control points.
 		<div>Returns an array with triplets of x, y, z coordinates that correspond to the current control points.
 		</div>
 		</div>
-		
+
 		<h3>[method:Object getLength]( [page:Integer nSubDivisions] )</h3>
 		<h3>[method:Object getLength]( [page:Integer nSubDivisions] )</h3>
 		<div>
 		<div>
 		nSubDivisions — number of subdivisions between control points. Default is *100*.
 		nSubDivisions — number of subdivisions between control points. Default is *100*.
@@ -54,7 +54,7 @@
 			the length of the spline when using nSubDivisions. The property .[page:Array chunkLength]
 			the length of the spline when using nSubDivisions. The property .[page:Array chunkLength]
 			contains an array with the total length from the beginning of the spline to the end of that chunk.
 			contains an array with the total length from the beginning of the spline to the end of that chunk.
 		</div>
 		</div>
-		
+
 		<h3>[method:null reparametrizeByArcLength]( [page:Float samplingCoef] )</h3>
 		<h3>[method:null reparametrizeByArcLength]( [page:Float samplingCoef] )</h3>
 		<div>
 		<div>
 		samplingCoef — how many intermediate values to use between spline points
 		samplingCoef — how many intermediate values to use between spline points

+ 3 - 3
docs/api/math/Triangle.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="en">
 <html lang="en">
 	<head>
 	<head>
-		<meta charset="utf-8" />
+		<meta charset="utf-8" />
 		<base href="../../" />
 		<base href="../../" />
 		<script src="list.js"></script>
 		<script src="list.js"></script>
 		<script src="page.js"></script>
 		<script src="page.js"></script>
@@ -35,7 +35,7 @@
 		<div>
 		<div>
 		The first [page:Vector3] of the triangle.
 		The first [page:Vector3] of the triangle.
 		</div>
 		</div>
-		
+
 		<h3>[property:Vector3 b]</h3>
 		<h3>[property:Vector3 b]</h3>
 		<div>
 		<div>
 		The second [page:Vector3] of the triangle.
 		The second [page:Vector3] of the triangle.
@@ -120,7 +120,7 @@
 		optionalTarget -- Optional [page:Plane] target to set the result.
 		optionalTarget -- Optional [page:Plane] target to set the result.
 		</div>
 		</div>
 		<div>
 		<div>
-		Return a [page:Plane plane] based on the triangle. Optionally sets a target plane. 
+		Return a [page:Plane plane] based on the triangle. Optionally sets a target plane.
 		</div>
 		</div>
 
 
 		<h3>[method:Boolean containsPoint]([page:Vector3 point])</h3>
 		<h3>[method:Boolean containsPoint]([page:Vector3 point])</h3>

+ 5 - 0
docs/api/math/Vector2.html

@@ -120,6 +120,11 @@
 		Normalizes this vector.
 		Normalizes this vector.
 		</div>
 		</div>
 
 
+		<h3>[method:Float angle]()</h3>
+		<div>
+		Computes the angle in radians of this vector with respect to the positive x-axis.
+		</div>
+
 		<h3>[method:Float distanceTo]( [page:Vector2 v] )</h3>
 		<h3>[method:Float distanceTo]( [page:Vector2 v] )</h3>
 		<div>
 		<div>
 		Computes distance of this vector to *v*.
 		Computes distance of this vector to *v*.

+ 2 - 2
docs/api/objects/Sprite.html

@@ -17,7 +17,7 @@
                 <h2>Example</h2>
                 <h2>Example</h2>
 
 
                 <code>
                 <code>
-                var map = THREE.ImageUtils.loadTexture( "sprite.png" );
+                var map = new THREE.TextureLoader().load( "sprite.png" );
                 var material = new THREE.SpriteMaterial( { map: map, color: 0xffffff, fog: true } );
                 var material = new THREE.SpriteMaterial( { map: map, color: 0xffffff, fog: true } );
                 var sprite = new THREE.Sprite( material );
                 var sprite = new THREE.Sprite( material );
                 scene.add( sprite );
                 scene.add( sprite );
@@ -48,7 +48,7 @@
                 <div>
                 <div>
                 This creates a new clone of the sprite.
                 This creates a new clone of the sprite.
                 </div>
                 </div>
-		
+
 		<h3>[method:Object3D clone]([page:Object3D object])</h3>
 		<h3>[method:Object3D clone]([page:Object3D object])</h3>
 		<div>
 		<div>
 		object -- (optional) Object3D which needs to be cloned. If undefined, clone method will create a new cloned Sprite Object.
 		object -- (optional) Object3D which needs to be cloned. If undefined, clone method will create a new cloned Sprite Object.

+ 10 - 10
docs/api/renderers/WebGLRenderer.html

@@ -98,11 +98,6 @@
 		<div>Default is THREE.CullFaceFront. The faces that needed to be culled. Possible values: THREE.CullFaceFront and THREE.CullFaceBack</div>
 		<div>Default is THREE.CullFaceFront. The faces that needed to be culled. Possible values: THREE.CullFaceFront and THREE.CullFaceBack</div>
 
 
 
 
-		<h3>[page:Boolean shadowMapDebug]</h3>
-
-		<div>Default is false. If set, then the shadowmaps get a specific color to identify which shadow is from which shadowmap.</div>
-
-
 		<h3>[property:Boolean shadowMapCascade]</h3>
 		<h3>[property:Boolean shadowMapCascade]</h3>
 
 
 		<div>Default is false. If Set, use cascaded shadowmaps. See [link:http://developer.download.nvidia.com/SDK/10.5/opengl/src/cascaded_shadow_maps/doc/cascaded_shadow_maps.pdf cascaded shadowmaps] for more information.</div>
 		<div>Default is false. If Set, use cascaded shadowmaps. See [link:http://developer.download.nvidia.com/SDK/10.5/opengl/src/cascaded_shadow_maps/doc/cascaded_shadow_maps.pdf cascaded shadowmaps] for more information.</div>
@@ -169,8 +164,8 @@
 		Return a [page:Boolean] true if the context supports vertex textures.
 		Return a [page:Boolean] true if the context supports vertex textures.
 		</div>
 		</div>
 
 
-		<h3>[method:null setSize]( [page:Integer width], [page:Integer height] )</h3>
-		<div>Resizes the output canvas to (width, height), and also sets the viewport to fit that size, starting in (0, 0).</div>
+		<h3>[method:null setSize]( [page:Integer width], [page:Integer height], [page:Boolean updateStyle] )</h3>
+		<div>Resizes the output canvas to (width, height), and also sets the viewport to fit that size, starting in (0, 0). Setting updateStyle to true adds explicit pixel units to the output canvas style.</div>
 
 
 		<h3>[method:null setViewport]( [page:Integer x], [page:Integer y], [page:Integer width], [page:Integer height] )</h3>
 		<h3>[method:null setViewport]( [page:Integer x], [page:Integer y], [page:Integer width], [page:Integer height] )</h3>
 		<div>Sets the viewport to render from (x, y) to (x + width, y + height).</div>
 		<div>Sets the viewport to render from (x, y) to (x + width, y + height).</div>
@@ -179,8 +174,10 @@
 		<h3>[method:null setScissor]( [page:Integer x], [page:Integer y], [page:Integer width], [page:Integer height] )</h3>
 		<h3>[method:null setScissor]( [page:Integer x], [page:Integer y], [page:Integer width], [page:Integer height] )</h3>
 		<div>Sets the scissor area from (x, y) to (x + width, y + height).</div>
 		<div>Sets the scissor area from (x, y) to (x + width, y + height).</div>
 
 
-		<h3>[method:null enableScissorTest]( [page:Boolean enable] )</h3>
-		<div>Enable the scissor test. When this is enabled, only the pixels within the defined scissor area will be affected by further renderer actions.</div>
+		<div>NOTE: The point (x, y) is the lower left corner of the area to be set for both of these methods. The area is defined from left to right in width but bottom to top in height. The sense of the vertical definition is opposite to the fill direction of an HTML canvas element.</div>
+
+		<h3>[method:null setScissorTest]( [page:Boolean boolean] )</h3>
+		<div>Enable or disable the scissor test. When this is enabled, only the pixels within the defined scissor area will be affected by further renderer actions.</div>
 
 
 		<h3>[method:null setClearColor]( [page:Color color], [page:Float alpha] )</h3>
 		<h3>[method:null setClearColor]( [page:Color color], [page:Float alpha] )</h3>
 		<div>Sets the clear color and opacity.</div>
 		<div>Sets the clear color and opacity.</div>
@@ -198,7 +195,7 @@
 		<div>Returns a [page:Float float] with the current clear alpha. Ranges from 0 to 1.</div>
 		<div>Returns a [page:Float float] with the current clear alpha. Ranges from 0 to 1.</div>
 
 
 		<h3>[method:null clear]( [page:Boolean color], [page:Boolean depth], [page:Boolean stencil] )</h3>
 		<h3>[method:null clear]( [page:Boolean color], [page:Boolean depth], [page:Boolean stencil] )</h3>
-		<div>Tells the renderer to clear its color, depth or stencil drawing buffer(s).</div>
+		<div>Tells the renderer to clear its color, depth or stencil drawing buffer(s). This method initializes the color buffer to the current clear color value.</div>
 		<div>Arguments default to true.</div>
 		<div>Arguments default to true.</div>
 
 
 		<h3>[method:null renderBufferImmediate]( [page:Object3D object], [page:shaderprogram program], [page:Material shading] )</h3>
 		<h3>[method:null renderBufferImmediate]( [page:Object3D object], [page:shaderprogram program], [page:Material shading] )</h3>
@@ -225,6 +222,9 @@
 		<div>If forceClear is true, the depth, stencil and color buffers will be cleared before rendering even if the renderer's autoClear property is false.</div>
 		<div>If forceClear is true, the depth, stencil and color buffers will be cleared before rendering even if the renderer's autoClear property is false.</div>
 		<div>Even with forceClear set to true you can prevent certain buffers being cleared by setting either the .autoClearColor, .autoClearStencil or .autoClearDepth properties to false.</div>
 		<div>Even with forceClear set to true you can prevent certain buffers being cleared by setting either the .autoClearColor, .autoClearStencil or .autoClearDepth properties to false.</div>
 
 
+		<h3>[method:null readRenderTargetPixels]( [page:WebGLRenderTarget renderTarget], [page:Float x], [page:Float y], [page:Float width], [page:Float height], buffer )</h3>
+		<div>Reads the pixel data from the renderTarget into the buffer you pass in. Buffer should be a Javascript Uint8Array instantiated with new Uint8Array( renderTargetWidth * renderTargetWidth * 4 ) to account for size and color information. This is a wrapper around gl.readPixels.</div>
+
 		<h3>[method:null renderImmediateObject]( camera, lights, fog, material, object )</h3>
 		<h3>[method:null renderImmediateObject]( camera, lights, fog, material, object )</h3>
 		<div>Renders an immediate Object using a camera.</div>
 		<div>Renders an immediate Object using a camera.</div>
 
 

+ 11 - 13
docs/api/textures/CubeTexture.html

@@ -1,7 +1,7 @@
 <!DOCTYPE html>
 <!DOCTYPE html>
 <html lang="en">
 <html lang="en">
 	<head>
 	<head>
-		<meta charset="utf-8" />
+		<meta charset="utf-8" />
 		<base href="../../" />
 		<base href="../../" />
 		<script src="list.js"></script>
 		<script src="list.js"></script>
 		<script src="page.js"></script>
 		<script src="page.js"></script>
@@ -17,15 +17,15 @@
 		<h2>Example</h2>
 		<h2>Example</h2>
 
 
 		<code>
 		<code>
-		var path = "textures/cube/pisa/";
-		var format = '.png';
-		var urls = [
-			path + 'px' + format, path + 'nx' + format,
-			path + 'py' + format, path + 'ny' + format,
-			path + 'pz' + format, path + 'nz' + format
-		];
-
-		var textureCube = THREE.ImageUtils.loadTextureCube( urls );
+		var loader = new THREE.CubeTextureLoader();
+		loader.setPath( 'textures/cube/pisa/' );
+		
+		var textureCube = loader.load( [
+			'px.png', 'nx.png',
+			'py.png', 'ny.png',
+			'pz.png', 'nz.png'
+		] );
+
 		var material = new THREE.MeshBasicMaterial( { color: 0xffffff, envMap: textureCube } );
 		var material = new THREE.MeshBasicMaterial( { color: 0xffffff, envMap: textureCube } );
 		</code>
 		</code>
 
 
@@ -37,9 +37,7 @@
 		<div>
 		<div>
 		CubeTexture is almost equivalent in functionality and usage to [page:Texture]. The only differences are that the
 		CubeTexture is almost equivalent in functionality and usage to [page:Texture]. The only differences are that the
 		images are an array of 6 images as opposed to a single image, and the mapping options are
 		images are an array of 6 images as opposed to a single image, and the mapping options are
-		[page:Textures THREE.CubeReflectionMapping] (default) or [page:Textures THREE.CubeRefractionMapping]<br />
-		<br />
-		Generally [page:ImageUtils.loadTextureCube] is used instead of using CubeTexture directly.
+		[page:Textures THREE.CubeReflectionMapping] (default) or [page:Textures THREE.CubeRefractionMapping]
 		</div>
 		</div>
 
 
 
 

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

@@ -21,7 +21,7 @@
 
 
 		<code>
 		<code>
 		// load a texture, set wrap mode to repeat
 		// load a texture, set wrap mode to repeat
-		var texture = THREE.ImageUtils.loadTexture( "textures/water.jpg" );
+		var texture = new THREE.TextureLoader().load( "textures/water.jpg" );
 		texture.wrapS = THREE.RepeatWrapping;
 		texture.wrapS = THREE.RepeatWrapping;
 		texture.wrapT = THREE.RepeatWrapping;
 		texture.wrapT = THREE.RepeatWrapping;
 		texture.repeat.set( 4, 4 );
 		texture.repeat.set( 4, 4 );
@@ -53,7 +53,7 @@
 		<div>
 		<div>
 		The default is THREE.ClampToEdgeWrapping, where the edge is clamped to the outer edge texels. The other two choices are THREE.RepeatWrapping and THREE.MirroredRepeatWrapping.
 		The default is THREE.ClampToEdgeWrapping, where the edge is clamped to the outer edge texels. The other two choices are THREE.RepeatWrapping and THREE.MirroredRepeatWrapping.
 		</div>
 		</div>
-		
+
 		<div>
 		<div>
 		NOTE: tiling of images in textures only functions if image dimensions are powers of two (2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, ...) in terms of pixels. Individual dimensions need not be equal, but each must be a power of two. This is a limitation of WebGL, not Three.js.
 		NOTE: tiling of images in textures only functions if image dimensions are powers of two (2, 4, 8, 16, 32, 64, 128, 256, 512, 1024, 2048, ...) in terms of pixels. Individual dimensions need not be equal, but each must be a power of two. This is a limitation of WebGL, not Three.js.
 		</div>
 		</div>

+ 10 - 12
docs/list.js

@@ -9,11 +9,11 @@ var list = {
 
 
 	"Reference": {
 	"Reference": {
 		"Constants": [
 		"Constants": [
-			[ "CustomBlendingEquation", "api/constants/CustomBlendingEquations"],
-			[ "GLState", "api/constants/GLState"],
-			[ "Materials", "api/constants/Materials"],
-			[ "ShadowingTypes", "api/constants/ShadowingTypes"],
-			[ "Textures", "api/constants/Textures"]
+			[ "CustomBlendingEquation", "api/constants/CustomBlendingEquations" ],
+			[ "GLState", "api/constants/GLState" ],
+			[ "Materials", "api/constants/Materials" ],
+			[ "ShadowingTypes", "api/constants/ShadowingTypes" ],
+			[ "Textures", "api/constants/Textures" ]
 		],
 		],
 
 
 		"Cameras": [
 		"Cameras": [
@@ -72,14 +72,13 @@ var list = {
 			[ "Material", "api/materials/Material" ],
 			[ "Material", "api/materials/Material" ],
 			[ "MeshBasicMaterial", "api/materials/MeshBasicMaterial" ],
 			[ "MeshBasicMaterial", "api/materials/MeshBasicMaterial" ],
 			[ "MeshDepthMaterial", "api/materials/MeshDepthMaterial" ],
 			[ "MeshDepthMaterial", "api/materials/MeshDepthMaterial" ],
-			[ "MeshFaceMaterial", "api/materials/MeshFaceMaterial" ],
+			[ "MultiMaterial", "api/materials/MultiMaterial" ],
 			[ "MeshLambertMaterial", "api/materials/MeshLambertMaterial" ],
 			[ "MeshLambertMaterial", "api/materials/MeshLambertMaterial" ],
 			[ "MeshNormalMaterial", "api/materials/MeshNormalMaterial" ],
 			[ "MeshNormalMaterial", "api/materials/MeshNormalMaterial" ],
 			[ "MeshPhongMaterial", "api/materials/MeshPhongMaterial" ],
 			[ "MeshPhongMaterial", "api/materials/MeshPhongMaterial" ],
 			[ "PointsMaterial", "api/materials/PointsMaterial" ],
 			[ "PointsMaterial", "api/materials/PointsMaterial" ],
 			[ "RawShaderMaterial", "api/materials/RawShaderMaterial" ],
 			[ "RawShaderMaterial", "api/materials/RawShaderMaterial" ],
 			[ "ShaderMaterial", "api/materials/ShaderMaterial" ],
 			[ "ShaderMaterial", "api/materials/ShaderMaterial" ],
-			[ "SpriteCanvasMaterial", "api/materials/SpriteCanvasMaterial" ],
 			[ "SpriteMaterial", "api/materials/SpriteMaterial" ]
 			[ "SpriteMaterial", "api/materials/SpriteMaterial" ]
 		],
 		],
 
 
@@ -151,16 +150,14 @@ var list = {
 		],
 		],
 
 
 		"Textures": [
 		"Textures": [
-			[ "CubeTexture", "api/textures/CubeTexture" ],
 			[ "CompressedTexture", "api/textures/CompressedTexture" ],
 			[ "CompressedTexture", "api/textures/CompressedTexture" ],
+			[ "CubeTexture", "api/textures/CubeTexture" ],
 			[ "DataTexture", "api/textures/DataTexture" ],
 			[ "DataTexture", "api/textures/DataTexture" ],
 			[ "Texture", "api/textures/Texture" ]
 			[ "Texture", "api/textures/Texture" ]
 		],
 		],
 
 
 		"Extras": [
 		"Extras": [
-			[ "FontUtils", "api/extras/FontUtils" ],
 			[ "GeometryUtils", "api/extras/GeometryUtils" ],
 			[ "GeometryUtils", "api/extras/GeometryUtils" ],
-			[ "ImageUtils", "api/extras/ImageUtils" ],
 			[ "SceneUtils", "api/extras/SceneUtils" ]
 			[ "SceneUtils", "api/extras/SceneUtils" ]
 		],
 		],
 
 
@@ -180,6 +177,7 @@ var list = {
 
 
 		"Extras / Curves": [
 		"Extras / Curves": [
 			[ "ArcCurve", "api/extras/curves/ArcCurve" ],
 			[ "ArcCurve", "api/extras/curves/ArcCurve" ],
+			[ "CatmullRomCurve3", "api/extras/curves/CatmullRomCurve3" ],
 			[ "ClosedSplineCurve3", "api/extras/curves/ClosedSplineCurve3" ],
 			[ "ClosedSplineCurve3", "api/extras/curves/ClosedSplineCurve3" ],
 			[ "CubicBezierCurve", "api/extras/curves/CubicBezierCurve" ],
 			[ "CubicBezierCurve", "api/extras/curves/CubicBezierCurve" ],
 			[ "CubicBezierCurve3", "api/extras/curves/CubicBezierCurve3" ],
 			[ "CubicBezierCurve3", "api/extras/curves/CubicBezierCurve3" ],
@@ -195,7 +193,6 @@ var list = {
 		"Extras / Geometries": [
 		"Extras / Geometries": [
 			[ "BoxGeometry", "api/extras/geometries/BoxGeometry" ],
 			[ "BoxGeometry", "api/extras/geometries/BoxGeometry" ],
 			[ "CircleGeometry", "api/extras/geometries/CircleGeometry" ],
 			[ "CircleGeometry", "api/extras/geometries/CircleGeometry" ],
-			[ "CubeGeometry", "api/extras/geometries/CubeGeometry" ],
 			[ "CylinderGeometry", "api/extras/geometries/CylinderGeometry" ],
 			[ "CylinderGeometry", "api/extras/geometries/CylinderGeometry" ],
 			[ "DodecahedronGeometry", "api/extras/geometries/DodecahedronGeometry" ],
 			[ "DodecahedronGeometry", "api/extras/geometries/DodecahedronGeometry" ],
 			[ "ExtrudeGeometry", "api/extras/geometries/ExtrudeGeometry" ],
 			[ "ExtrudeGeometry", "api/extras/geometries/ExtrudeGeometry" ],
@@ -239,7 +236,8 @@ var list = {
 
 
 		"Examples" : [
 		"Examples" : [
 			[ "CombinedCamera", "api/examples/cameras/CombinedCamera" ],
 			[ "CombinedCamera", "api/examples/cameras/CombinedCamera" ],
-			[ "LookupTable", "api/examples/Lut" ]
+			[ "LookupTable", "api/examples/Lut" ],
+			[ "SpriteCanvasMaterial", "api/examples/SpriteCanvasMaterial" ]
 
 
 		]
 		]
 
 

+ 14 - 14
docs/page.js

@@ -7,7 +7,7 @@ var onDocumentLoad = function ( event ) {
 
 
 	if ( section == 'manual' ) {
 	if ( section == 'manual' ) {
 
 
-		name = name.replace(/\-/g, ' ');
+		name = name.replace( /\-/g, ' ' );
 
 
 		path = pathname.replace( /\ /g, '-' );
 		path = pathname.replace( /\ /g, '-' );
 		path = /\/manual\/[-A-z0-9\/]+/.exec( path ).toString().substr( 8 );
 		path = /\/manual\/[-A-z0-9\/]+/.exec( path ).toString().substr( 8 );
@@ -20,22 +20,22 @@ var onDocumentLoad = function ( event ) {
 
 
 	var text = document.body.innerHTML;
 	var text = document.body.innerHTML;
 
 
-	text = text.replace(/\[name\]/gi, name);
-	text = text.replace(/\[path\]/gi, path);
-	text = text.replace(/\[page:([\w\.]+)\]/gi, "[page:$1 $1]" ); // [page:name] to [page:name title]
-	text = text.replace(/\[page:\.([\w\.]+) ([\w\.\s]+)\]/gi, "[page:"+name+".$1 $2]" ); // [page:.member title] to [page:name.member title]
-	text = text.replace(/\[page:([\w\.]+) ([\w\.\s]+)\]/gi, "<a href=\"javascript:window.parent.goTo('$1')\" title=\"$1\">$2</a>" ); // [page:name title]
-	// text = text.replace(/\[member:.([\w]+) ([\w\.\s]+)\]/gi, "<a href=\"javascript:window.parent.goTo('" + name + ".$1')\" title=\"$1\">$2</a>" );
+	text = text.replace( /\[name\]/gi, name );
+	text = text.replace( /\[path\]/gi, path );
+	text = text.replace( /\[page:([\w\.]+)\]/gi, "[page:$1 $1]" ); // [page:name] to [page:name title]
+	text = text.replace( /\[page:\.([\w\.]+) ([\w\.\s]+)\]/gi, "[page:" + name + ".$1 $2]" ); // [page:.member title] to [page:name.member title]
+	text = text.replace( /\[page:([\w\.]+) ([\w\.\s]+)\]/gi, "<a href=\"javascript:window.parent.goTo('$1')\" title=\"$1\">$2</a>" ); // [page:name title]
+	// text = text.replace( /\[member:.([\w]+) ([\w\.\s]+)\]/gi, "<a href=\"javascript:window.parent.goTo('" + name + ".$1')\" title=\"$1\">$2</a>" );
 
 
-	text = text.replace(/\[(?:member|property|method):([\w]+)\]/gi, "[member:$1 $1]" ); // [member:name] to [member:name title]
-	text = text.replace(/\[(?:member|property|method):([\w]+) ([\w\.\s]+)\]/gi, "<a href=\"javascript:window.parent.goTo('"+name+".$2')\" target=\"_parent\" title=\""+name+".$2\" class=\"permalink\">#</a> .<a href=\"javascript:window.parent.goTo('$1')\" title=\"$1\" id=\"$2\">$2</a> " );
+	text = text.replace( /\[(?:member|property|method):([\w]+)\]/gi, "[member:$1 $1]" ); // [member:name] to [member:name title]
+	text = text.replace( /\[(?:member|property|method):([\w]+) ([\w\.\s]+)\]/gi, "<a href=\"javascript:window.parent.goTo('" + name + ".$2')\" target=\"_parent\" title=\"" + name + ".$2\" class=\"permalink\">#</a> .<a href=\"javascript:window.parent.goTo('$1')\" title=\"$1\" id=\"$2\">$2</a> " );
 
 
-	text = text.replace(/\[link:([\w|\:|\/|\.|\-|\_]+)\]/gi, "[link:$1 $1]" ); // [link:url] to [link:url title]
-	text = text.replace(/\[link:([\w|\:|\/|\.|\-|\_|\(|\)|\#]+) ([\w|\:|\/|\.|\-|\_|\s]+)\]/gi, "<a href=\"$1\"  target=\"_blank\">$2</a>" ); // [link:url title]
-	text = text.replace(/\*([\w|\d|\"|\-|\(][\w|\d|\ |\-|\/|\+|\-|\(|\)|\=|\,|\.\"]*[\w|\d|\"|\)]|\w)\*/gi, "<strong>$1</strong>" ); // *
+	text = text.replace( /\[link:([\w|\:|\/|\.|\-|\_]+)\]/gi, "[link:$1 $1]" ); // [link:url] to [link:url title]
+	text = text.replace( /\[link:([\w|\:|\/|\.|\-|\_|\(|\)|\#]+) ([\w|\:|\/|\.|\-|\_|\s]+)\]/gi, "<a href=\"$1\"  target=\"_blank\">$2</a>" ); // [link:url title]
+	text = text.replace( /\*([\w|\d|\"|\-|\(][\w|\d|\ |\-|\/|\+|\-|\(|\)|\=|\,|\.\"]*[\w|\d|\"|\)]|\w)\*/gi, "<strong>$1</strong>" ); // *
 
 
-	text = text.replace(/\[example:([\w\_]+)\]/gi, "[example:$1 $1]" ); // [example:name] to [example:name title]
-	text = text.replace(/\[example:([\w\_]+) ([\w\:\/\.\-\_ \s]+)\]/gi, "<a href=\"../../../../examples/#$1\"  target=\"_blank\">$2</a>" ); // [example:name title]
+	text = text.replace( /\[example:([\w\_]+)\]/gi, "[example:$1 $1]" ); // [example:name] to [example:name title]
+	text = text.replace( /\[example:([\w\_]+) ([\w\:\/\.\-\_ \s]+)\]/gi, "<a href=\"../examples/#$1\"  target=\"_blank\">$2</a>" ); // [example:name title]
 
 
 
 
 	document.body.innerHTML = text;
 	document.body.innerHTML = text;

+ 34 - 33
docs/scenes/bones-browser.html

@@ -10,7 +10,7 @@
 				font-weight: normal;
 				font-weight: normal;
 				font-style: normal;
 				font-style: normal;
 			}
 			}
-			
+
 			body {
 			body {
 				margin:0;
 				margin:0;
 				font-family: 'inconsolata';
 				font-family: 'inconsolata';
@@ -18,9 +18,9 @@
 				line-height: 18px;
 				line-height: 18px;
 				overflow: hidden;
 				overflow: hidden;
 			}
 			}
-			
+
 			canvas { width: 100%; height: 100% }
 			canvas { width: 100%; height: 100% }
-			
+
 			#newWindow {
 			#newWindow {
 				display: block;
 				display: block;
 				position: absolute;
 				position: absolute;
@@ -31,13 +31,13 @@
 		</style>
 		</style>
 	</head>
 	</head>
 	<body>
 	<body>
-		
+
 		<a id='newWindow' href='./bones-browser.html' target='_blank'>Open in New Window</a>
 		<a id='newWindow' href='./bones-browser.html' target='_blank'>Open in New Window</a>
-		
+
 		<script src="../../build/three.min.js"></script>
 		<script src="../../build/three.min.js"></script>
 		<script src='../../examples/js/libs/dat.gui.min.js'></script>
 		<script src='../../examples/js/libs/dat.gui.min.js'></script>
 		<script src="../../examples/js/controls/OrbitControls.js"></script>
 		<script src="../../examples/js/controls/OrbitControls.js"></script>
-		
+
 		<script>
 		<script>
 
 
 			var gui, scene, camera, renderer, orbit, ambientLight, lights, mesh, bones, skeletonHelper;
 			var gui, scene, camera, renderer, orbit, ambientLight, lights, mesh, bones, skeletonHelper;
@@ -57,6 +57,7 @@
 				camera.position.y = 30;
 				camera.position.y = 30;
 
 
 				renderer = new THREE.WebGLRenderer( { antialias: true } );
 				renderer = new THREE.WebGLRenderer( { antialias: true } );
+				renderer.setPixelRatio( window.devicePixelRatio );				
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				document.body.appendChild( renderer.domElement );
 				document.body.appendChild( renderer.domElement );
 
 
@@ -155,9 +156,9 @@
 
 
 				var mesh = new THREE.SkinnedMesh( geometry,	material );
 				var mesh = new THREE.SkinnedMesh( geometry,	material );
 				var skeleton = new THREE.Skeleton( bones );
 				var skeleton = new THREE.Skeleton( bones );
-	
+
 				mesh.add( bones[ 0 ] );
 				mesh.add( bones[ 0 ] );
-	
+
 				mesh.bind( skeleton );
 				mesh.bind( skeleton );
 
 
 				skeletonHelper = new THREE.SkeletonHelper( mesh );
 				skeletonHelper = new THREE.SkeletonHelper( mesh );
@@ -165,27 +166,27 @@
 				scene.add( skeletonHelper );
 				scene.add( skeletonHelper );
 
 
 				return mesh;
 				return mesh;
-	
+
 			};
 			};
 
 
 			function setupDatGui () {
 			function setupDatGui () {
-	
+
 				var folder = gui.addFolder( "General Options" );
 				var folder = gui.addFolder( "General Options" );
-	
+
 				folder.add( state, "animateBones" );
 				folder.add( state, "animateBones" );
 				folder.__controllers[ 0 ].name( "Animate Bones" );
 				folder.__controllers[ 0 ].name( "Animate Bones" );
 
 
 				folder.add( mesh, "pose" );
 				folder.add( mesh, "pose" );
 				folder.__controllers[ 1 ].name( ".pose()" );
 				folder.__controllers[ 1 ].name( ".pose()" );
-	
+
 				var bones = mesh.skeleton.bones;
 				var bones = mesh.skeleton.bones;
-	
+
 				for ( var i = 0; i < bones.length; i ++ ) {
 				for ( var i = 0; i < bones.length; i ++ ) {
-		
+
 					var bone = bones[ i ];
 					var bone = bones[ i ];
-		
+
 					folder = gui.addFolder( "Bone " + i );
 					folder = gui.addFolder( "Bone " + i );
-	
+
 					folder.add( bone.position, 'x', -10 + bone.position.x, 10 + bone.position.x );
 					folder.add( bone.position, 'x', -10 + bone.position.x, 10 + bone.position.x );
 					folder.add( bone.position, 'y', -10 + bone.position.y, 10 + bone.position.y );
 					folder.add( bone.position, 'y', -10 + bone.position.y, 10 + bone.position.y );
 					folder.add( bone.position, 'z', -10 + bone.position.z, 10 + bone.position.z );
 					folder.add( bone.position, 'z', -10 + bone.position.z, 10 + bone.position.z );
@@ -193,7 +194,7 @@
 					folder.add( bone.rotation, 'x', -Math.PI * 0.5, Math.PI * 0.5 );
 					folder.add( bone.rotation, 'x', -Math.PI * 0.5, Math.PI * 0.5 );
 					folder.add( bone.rotation, 'y', -Math.PI * 0.5, Math.PI * 0.5 );
 					folder.add( bone.rotation, 'y', -Math.PI * 0.5, Math.PI * 0.5 );
 					folder.add( bone.rotation, 'z', -Math.PI * 0.5, Math.PI * 0.5 );
 					folder.add( bone.rotation, 'z', -Math.PI * 0.5, Math.PI * 0.5 );
-		
+
 					folder.add( bone.scale, 'x', 0, 2 );
 					folder.add( bone.scale, 'x', 0, 2 );
 					folder.add( bone.scale, 'y', 0, 2 );
 					folder.add( bone.scale, 'y', 0, 2 );
 					folder.add( bone.scale, 'z', 0, 2 );
 					folder.add( bone.scale, 'z', 0, 2 );
@@ -201,21 +202,21 @@
 					folder.__controllers[ 0 ].name( "position.x" );
 					folder.__controllers[ 0 ].name( "position.x" );
 					folder.__controllers[ 1 ].name( "position.y" );
 					folder.__controllers[ 1 ].name( "position.y" );
 					folder.__controllers[ 2 ].name( "position.z" );
 					folder.__controllers[ 2 ].name( "position.z" );
-		
+
 					folder.__controllers[ 3 ].name( "rotation.x" );
 					folder.__controllers[ 3 ].name( "rotation.x" );
 					folder.__controllers[ 4 ].name( "rotation.y" );
 					folder.__controllers[ 4 ].name( "rotation.y" );
 					folder.__controllers[ 5 ].name( "rotation.z" );
 					folder.__controllers[ 5 ].name( "rotation.z" );
-		
+
 					folder.__controllers[ 6 ].name( "scale.x" );
 					folder.__controllers[ 6 ].name( "scale.x" );
 					folder.__controllers[ 7 ].name( "scale.y" );
 					folder.__controllers[ 7 ].name( "scale.y" );
 					folder.__controllers[ 8 ].name( "scale.z" );
 					folder.__controllers[ 8 ].name( "scale.z" );
-		
+
 				}
 				}
-				
+
 			}
 			}
 
 
 			function initBones () {
 			function initBones () {
-	
+
 				var segmentHeight = 8;
 				var segmentHeight = 8;
 				var segmentCount = 4;
 				var segmentCount = 4;
 				var height = segmentHeight * segmentCount;
 				var height = segmentHeight * segmentCount;
@@ -238,34 +239,34 @@
 			};
 			};
 
 
 			function render () {
 			function render () {
-	
+
 				requestAnimationFrame( render );
 				requestAnimationFrame( render );
 
 
 				var time = Date.now() * 0.001;
 				var time = Date.now() * 0.001;
-	
+
 				var bone = mesh;
 				var bone = mesh;
 
 
-	
+
 				//Wiggle the bones
 				//Wiggle the bones
 				if ( state.animateBones ) {
 				if ( state.animateBones ) {
-		
+
 					for ( var i = 0; i < mesh.skeleton.bones.length; i ++ ) {
 					for ( var i = 0; i < mesh.skeleton.bones.length; i ++ ) {
-		
+
 						mesh.skeleton.bones[ i ].rotation.z = Math.sin( time ) * 2 / mesh.skeleton.bones.length;
 						mesh.skeleton.bones[ i ].rotation.z = Math.sin( time ) * 2 / mesh.skeleton.bones.length;
-		
+
 					}
 					}
-		
+
 				}
 				}
 
 
 				skeletonHelper.update();
 				skeletonHelper.update();
-	
+
 				renderer.render( scene, camera );
 				renderer.render( scene, camera );
-	
+
 			};
 			};
 
 
 			initScene();
 			initScene();
 			render();
 			render();
-			
+
 		</script>
 		</script>
 	</body>
 	</body>
-</html>
+</html>

+ 2 - 11
docs/scenes/geometry-browser.html

@@ -37,16 +37,6 @@
 		<script src="../../build/three.min.js"></script>
 		<script src="../../build/three.min.js"></script>
 		<script src='../../examples/js/libs/dat.gui.min.js'></script>
 		<script src='../../examples/js/libs/dat.gui.min.js'></script>
 		<script src="../../examples/js/controls/OrbitControls.js"></script>
 		<script src="../../examples/js/controls/OrbitControls.js"></script>
-		<script src="../../examples/js/geometries/TextGeometry.js"></script>
-		<script src="../../examples/js/utils/FontUtils.js"></script>
-		<script src="../../examples/fonts/gentilis_bold.typeface.js"></script>
-		<script src="../../examples/fonts/gentilis_regular.typeface.js"></script>
-		<script src="../../examples/fonts/optimer_bold.typeface.js"></script>
-		<script src="../../examples/fonts/optimer_regular.typeface.js"></script>
-		<script src="../../examples/fonts/helvetiker_bold.typeface.js"></script>
-		<script src="../../examples/fonts/helvetiker_regular.typeface.js"></script>
-		<script src="../../examples/fonts/droid/droid_serif_regular.typeface.js"></script>
-		<script src="../../examples/fonts/droid/droid_serif_bold.typeface.js"></script>
 
 
 		<script src='js/geometry.js'></script>
 		<script src='js/geometry.js'></script>
 
 
@@ -59,7 +49,8 @@
 			var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 50 );
 			var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 50 );
 			camera.position.z = 30;
 			camera.position.z = 30;
 
 
-			var renderer = new THREE.WebGLRenderer({antialias: true});
+			var renderer = new THREE.WebGLRenderer( { antialias: true } );
+			renderer.setPixelRatio( window.devicePixelRatio );
 			renderer.setSize( window.innerWidth, window.innerHeight );
 			renderer.setSize( window.innerWidth, window.innerHeight );
 			document.body.appendChild( renderer.domElement );
 			document.body.appendChild( renderer.domElement );
 
 

+ 94 - 76
docs/scenes/js/geometry.js

@@ -77,17 +77,18 @@ var constants = {
 
 
 	}
 	}
 
 
-}
+};
 
 
 function updateGroupGeometry( mesh, geometry ) {
 function updateGroupGeometry( mesh, geometry ) {
 
 
-	mesh.children[0].geometry.dispose();
-	mesh.children[1].geometry.dispose();
+	mesh.children[ 0 ].geometry.dispose();
+	mesh.children[ 1 ].geometry.dispose();
+
+	mesh.children[ 0 ].geometry = new THREE.WireframeGeometry( geometry );
+	mesh.children[ 1 ].geometry = geometry;
 
 
-	mesh.children[0].geometry = new THREE.WireframeGeometry( geometry );
-	mesh.children[1].geometry = geometry;
+	// these do not update nicely together if shared
 
 
-	//these do not update nicely together if shared
 }
 }
 
 
 var guis = {
 var guis = {
@@ -113,16 +114,17 @@ var guis = {
 
 
 		}
 		}
 
 
-		var folder = gui.addFolder('THREE.BoxGeometry');
+		var folder = gui.addFolder( 'THREE.BoxGeometry' );
 
 
 		folder.add( data, 'width', 1, 30 ).onChange( generateGeometry );
 		folder.add( data, 'width', 1, 30 ).onChange( generateGeometry );
 		folder.add( data, 'height', 1, 30 ).onChange( generateGeometry );
 		folder.add( data, 'height', 1, 30 ).onChange( generateGeometry );
 		folder.add( data, 'depth', 1, 30 ).onChange( generateGeometry );
 		folder.add( data, 'depth', 1, 30 ).onChange( generateGeometry );
-		folder.add( data, 'widthSegments', 1, 10 ).step(1).onChange( generateGeometry );
-		folder.add( data, 'heightSegments', 1, 10 ).step(1).onChange( generateGeometry );
-		folder.add( data, 'depthSegments', 1, 10 ).step(1).onChange( generateGeometry );
+		folder.add( data, 'widthSegments', 1, 10 ).step( 1 ).onChange( generateGeometry );
+		folder.add( data, 'heightSegments', 1, 10 ).step( 1 ).onChange( generateGeometry );
+		folder.add( data, 'depthSegments', 1, 10 ).step( 1 ).onChange( generateGeometry );
 
 
 		generateGeometry();
 		generateGeometry();
+
 	},
 	},
 
 
 	CylinderGeometry : function( mesh ) {
 	CylinderGeometry : function( mesh ) {
@@ -151,23 +153,24 @@ var guis = {
 					data.thetaStart,
 					data.thetaStart,
 					data.thetaLength
 					data.thetaLength
 				)
 				)
-			)
+			);
 
 
 		}
 		}
 
 
-		var folder = gui.addFolder('THREE.CylinderGeometry');
+		var folder = gui.addFolder( 'THREE.CylinderGeometry' );
 
 
 		folder.add( data, 'radiusTop', 1, 30 ).onChange( generateGeometry );
 		folder.add( data, 'radiusTop', 1, 30 ).onChange( generateGeometry );
 		folder.add( data, 'radiusBottom', 1, 30 ).onChange( generateGeometry );
 		folder.add( data, 'radiusBottom', 1, 30 ).onChange( generateGeometry );
 		folder.add( data, 'height', 1, 50 ).onChange( generateGeometry );
 		folder.add( data, 'height', 1, 50 ).onChange( generateGeometry );
-		folder.add( data, 'radiusSegments', 3, 64 ).step(1).onChange( generateGeometry );
-		folder.add( data, 'heightSegments', 1, 64 ).step(1).onChange( generateGeometry );
+		folder.add( data, 'radiusSegments', 3, 64 ).step( 1 ).onChange( generateGeometry );
+		folder.add( data, 'heightSegments', 1, 64 ).step( 1 ).onChange( generateGeometry );
 		folder.add( data, 'openEnded' ).onChange( generateGeometry );
 		folder.add( data, 'openEnded' ).onChange( generateGeometry );
 		folder.add( data, 'thetaStart', 0, twoPi ).onChange( generateGeometry );
 		folder.add( data, 'thetaStart', 0, twoPi ).onChange( generateGeometry );
 		folder.add( data, 'thetaLength', 0, twoPi ).onChange( generateGeometry );
 		folder.add( data, 'thetaLength', 0, twoPi ).onChange( generateGeometry );
 
 
 
 
 		generateGeometry();
 		generateGeometry();
+
 	},
 	},
 
 
 	CircleGeometry : function( mesh ) {
 	CircleGeometry : function( mesh ) {
@@ -189,14 +192,15 @@ var guis = {
 
 
 		}
 		}
 
 
-		var folder = gui.addFolder('THREE.CircleGeometry');
+		var folder = gui.addFolder( 'THREE.CircleGeometry' );
 
 
 		folder.add( data, 'radius', 1, 20 ).onChange( generateGeometry );
 		folder.add( data, 'radius', 1, 20 ).onChange( generateGeometry );
-		folder.add( data, 'segments', 0, 128 ).step(1).onChange( generateGeometry );
+		folder.add( data, 'segments', 0, 128 ).step( 1 ).onChange( generateGeometry );
 		folder.add( data, 'thetaStart', 0, twoPi ).onChange( generateGeometry );
 		folder.add( data, 'thetaStart', 0, twoPi ).onChange( generateGeometry );
 		folder.add( data, 'thetaLength', 0, twoPi ).onChange( generateGeometry );
 		folder.add( data, 'thetaLength', 0, twoPi ).onChange( generateGeometry );
 
 
 		generateGeometry();
 		generateGeometry();
+
 	},
 	},
 
 
 	DodecahedronGeometry : function() {
 	DodecahedronGeometry : function() {
@@ -212,16 +216,16 @@ var guis = {
 				new THREE.DodecahedronGeometry(
 				new THREE.DodecahedronGeometry(
 					data.radius, data.detail
 					data.radius, data.detail
 				)
 				)
-			)
+			);
 
 
 		}
 		}
 
 
-		var folder = gui.addFolder('THREE.DodecahedronGeometry');
+		var folder = gui.addFolder( 'THREE.DodecahedronGeometry' );
 
 
-		folder.add( data, 'radius', 1, 20 ).onChange( generateGeometry )
-		folder.add( data, 'detail', 0, 5 ).step(1).onChange( generateGeometry )
+		folder.add( data, 'radius', 1, 20 ).onChange( generateGeometry );
+		folder.add( data, 'detail', 0, 5 ).step( 1 ).onChange( generateGeometry );
 
 
-		generateGeometry()
+		generateGeometry();
 
 
 	},
 	},
 
 
@@ -238,16 +242,16 @@ var guis = {
 				new THREE.IcosahedronGeometry(
 				new THREE.IcosahedronGeometry(
 					data.radius, data.detail
 					data.radius, data.detail
 				)
 				)
-			)
+			);
 
 
 		}
 		}
 
 
-		var folder = gui.addFolder('THREE.IcosahedronGeometry');
+		var folder = gui.addFolder( 'THREE.IcosahedronGeometry' );
 
 
-		folder.add( data, 'radius', 1, 20 ).onChange( generateGeometry )
-		folder.add( data, 'detail', 0, 5 ).step(1).onChange( generateGeometry )
+		folder.add( data, 'radius', 1, 20 ).onChange( generateGeometry );
+		folder.add( data, 'detail', 0, 5 ).step( 1 ).onChange( generateGeometry );
 
 
-		generateGeometry()
+		generateGeometry();
 
 
 	},
 	},
 
 
@@ -264,16 +268,16 @@ var guis = {
 				new THREE.OctahedronGeometry(
 				new THREE.OctahedronGeometry(
 					data.radius, data.detail
 					data.radius, data.detail
 				)
 				)
-			)
+			);
 
 
 		}
 		}
 
 
-		var folder = gui.addFolder('THREE.OctahedronGeometry');
+		var folder = gui.addFolder( 'THREE.OctahedronGeometry' );
 
 
-		folder.add( data, 'radius', 1, 20 ).onChange( generateGeometry )
-		folder.add( data, 'detail', 0, 5 ).step(1).onChange( generateGeometry )
+		folder.add( data, 'radius', 1, 20 ).onChange( generateGeometry );
+		folder.add( data, 'detail', 0, 5 ).step( 1 ).onChange( generateGeometry );
 
 
-		generateGeometry()
+		generateGeometry();
 
 
 	},
 	},
 
 
@@ -296,14 +300,15 @@ var guis = {
 
 
 		}
 		}
 
 
-		var folder = gui.addFolder('THREE.PlaneGeometry');
+		var folder = gui.addFolder( 'THREE.PlaneGeometry' );
 
 
 		folder.add( data, 'width', 1, 30 ).onChange( generateGeometry );
 		folder.add( data, 'width', 1, 30 ).onChange( generateGeometry );
 		folder.add( data, 'height', 1, 30 ).onChange( generateGeometry );
 		folder.add( data, 'height', 1, 30 ).onChange( generateGeometry );
-		folder.add( data, 'widthSegments', 1, 30 ).step(1).onChange( generateGeometry );
-		folder.add( data, 'heightSegments', 1, 30 ).step(1).onChange( generateGeometry );
+		folder.add( data, 'widthSegments', 1, 30 ).step( 1 ).onChange( generateGeometry );
+		folder.add( data, 'heightSegments', 1, 30 ).step( 1 ).onChange( generateGeometry );
 
 
 		generateGeometry();
 		generateGeometry();
+
 	},
 	},
 
 
 	RingGeometry : function( mesh ) {
 	RingGeometry : function( mesh ) {
@@ -327,16 +332,17 @@ var guis = {
 
 
 		}
 		}
 
 
-		var folder = gui.addFolder('THREE.RingGeometry');
+		var folder = gui.addFolder( 'THREE.RingGeometry' );
 
 
 		folder.add( data, 'innerRadius', 0, 30 ).onChange( generateGeometry );
 		folder.add( data, 'innerRadius', 0, 30 ).onChange( generateGeometry );
 		folder.add( data, 'outerRadius', 1, 30 ).onChange( generateGeometry );
 		folder.add( data, 'outerRadius', 1, 30 ).onChange( generateGeometry );
-		folder.add( data, 'thetaSegments', 1, 30 ).step(1).onChange( generateGeometry );
-		folder.add( data, 'phiSegments', 1, 30 ).step(1).onChange( generateGeometry );
+		folder.add( data, 'thetaSegments', 1, 30 ).step( 1 ).onChange( generateGeometry );
+		folder.add( data, 'phiSegments', 1, 30 ).step( 1 ).onChange( generateGeometry );
 		folder.add( data, 'thetaStart', 0, twoPi ).onChange( generateGeometry );
 		folder.add( data, 'thetaStart', 0, twoPi ).onChange( generateGeometry );
 		folder.add( data, 'thetaLength', 0, twoPi ).onChange( generateGeometry );
 		folder.add( data, 'thetaLength', 0, twoPi ).onChange( generateGeometry );
 
 
 		generateGeometry();
 		generateGeometry();
+
 	},
 	},
 
 
 	SphereGeometry : function( mesh ) {
 	SphereGeometry : function( mesh ) {
@@ -361,17 +367,18 @@ var guis = {
 
 
 		}
 		}
 
 
-		var folder = gui.addFolder('THREE.SphereGeometry');
+		var folder = gui.addFolder( 'THREE.SphereGeometry' );
 
 
 		folder.add( data, 'radius', 1, 30 ).onChange( generateGeometry );
 		folder.add( data, 'radius', 1, 30 ).onChange( generateGeometry );
-		folder.add( data, 'widthSegments', 3, 32 ).step(1).onChange( generateGeometry );
-		folder.add( data, 'heightSegments', 2, 32 ).step(1).onChange( generateGeometry );
+		folder.add( data, 'widthSegments', 3, 32 ).step( 1 ).onChange( generateGeometry );
+		folder.add( data, 'heightSegments', 2, 32 ).step( 1 ).onChange( generateGeometry );
 		folder.add( data, 'phiStart', 0, twoPi ).onChange( generateGeometry );
 		folder.add( data, 'phiStart', 0, twoPi ).onChange( generateGeometry );
 		folder.add( data, 'phiLength', 0, twoPi ).onChange( generateGeometry );
 		folder.add( data, 'phiLength', 0, twoPi ).onChange( generateGeometry );
 		folder.add( data, 'thetaStart', 0, twoPi ).onChange( generateGeometry );
 		folder.add( data, 'thetaStart', 0, twoPi ).onChange( generateGeometry );
 		folder.add( data, 'thetaLength', 0, twoPi ).onChange( generateGeometry );
 		folder.add( data, 'thetaLength', 0, twoPi ).onChange( generateGeometry );
 
 
 		generateGeometry();
 		generateGeometry();
+
 	},
 	},
 
 
 	TetrahedronGeometry : function() {
 	TetrahedronGeometry : function() {
@@ -387,16 +394,16 @@ var guis = {
 				new THREE.TetrahedronGeometry(
 				new THREE.TetrahedronGeometry(
 					data.radius, data.detail
 					data.radius, data.detail
 				)
 				)
-			)
+			);
 
 
 		}
 		}
 
 
-		var folder = gui.addFolder('THREE.TetrahedronGeometry');
+		var folder = gui.addFolder( 'THREE.TetrahedronGeometry' );
 
 
-		folder.add( data, 'radius', 1, 20 ).onChange( generateGeometry )
-		folder.add( data, 'detail', 0, 5 ).step(1).onChange( generateGeometry )
+		folder.add( data, 'radius', 1, 20 ).onChange( generateGeometry );
+		folder.add( data, 'detail', 0, 5 ).step( 1 ).onChange( generateGeometry );
 
 
-		generateGeometry()
+		generateGeometry();
 
 
 	},
 	},
 
 
@@ -408,8 +415,7 @@ var guis = {
 			height : 2,
 			height : 2,
 			curveSegments : 12,
 			curveSegments : 12,
 			font : "helvetiker",
 			font : "helvetiker",
-			weight : "normal",
-			style : "normal",
+			weight : "regular",
 			bevelEnabled : false,
 			bevelEnabled : false,
 			bevelThickness : 1,
 			bevelThickness : 1,
 			bevelSize : 0.5
 			bevelSize : 0.5
@@ -419,40 +425,52 @@ var guis = {
 			"helvetiker",
 			"helvetiker",
 			"optimer",
 			"optimer",
 			"gentilis",
 			"gentilis",
-			"droid serif"
-		]
+			"droid/droid_serif"
+		];
 
 
 		var weights = [
 		var weights = [
-			"normal", "bold"
-		]
+			"regular", "bold"
+		];
 
 
 		function generateGeometry() {
 		function generateGeometry() {
 
 
-			var geometry = new THREE.TextGeometry( data.text, data )
+			var loader = new THREE.FontLoader();
+			loader.load( '../../examples/fonts/' + data.font + '_' + data.weight + '.typeface.js', function ( font ) {
+
+				var geometry = new THREE.TextGeometry( data.text, {
+					font: font,
+					size: data.size,
+					height: data.height,
+					curveSegments: data.curveSegments,
+					bevelEnabled: data.bevelEnabled,
+					bevelThickness: data.bevelThickness,
+					bevelSize: data.bevelSize
+				} );
+				geometry.center();
 
 
-			geometry.center()
+				updateGroupGeometry( mesh, geometry );
 
 
-			updateGroupGeometry( mesh, geometry );
+			} );
 
 
 		}
 		}
 
 
 		//Hide the wireframe
 		//Hide the wireframe
-		mesh.children[0].visible = false;
+		mesh.children[ 0 ].visible = false;
 
 
-		var folder = gui.addFolder('THREE.TextGeometry');
+		var folder = gui.addFolder( 'THREE.TextGeometry' );
 
 
 		folder.add( data, 'text' ).onChange( generateGeometry );
 		folder.add( data, 'text' ).onChange( generateGeometry );
 		folder.add( data, 'size', 1, 30 ).onChange( generateGeometry );
 		folder.add( data, 'size', 1, 30 ).onChange( generateGeometry );
 		folder.add( data, 'height', 1, 20 ).onChange( generateGeometry );
 		folder.add( data, 'height', 1, 20 ).onChange( generateGeometry );
-		folder.add( data, 'curveSegments', 1, 20 ).step(1).onChange( generateGeometry );
+		folder.add( data, 'curveSegments', 1, 20 ).step( 1 ).onChange( generateGeometry );
 		folder.add( data, 'font', fonts ).onChange( generateGeometry );
 		folder.add( data, 'font', fonts ).onChange( generateGeometry );
 		folder.add( data, 'weight', weights ).onChange( generateGeometry );
 		folder.add( data, 'weight', weights ).onChange( generateGeometry );
-		// folder.add( data, 'style', 1, 1 ).onChange( generateGeometry );
 		folder.add( data, 'bevelEnabled' ).onChange( generateGeometry );
 		folder.add( data, 'bevelEnabled' ).onChange( generateGeometry );
 		folder.add( data, 'bevelThickness', 0.1, 3 ).onChange( generateGeometry );
 		folder.add( data, 'bevelThickness', 0.1, 3 ).onChange( generateGeometry );
 		folder.add( data, 'bevelSize', 0.1, 3 ).onChange( generateGeometry );
 		folder.add( data, 'bevelSize', 0.1, 3 ).onChange( generateGeometry );
 
 
 		generateGeometry();
 		generateGeometry();
+
 	},
 	},
 
 
 	TorusGeometry : function( mesh ) {
 	TorusGeometry : function( mesh ) {
@@ -471,16 +489,16 @@ var guis = {
 				new THREE.TorusGeometry(
 				new THREE.TorusGeometry(
 					data.radius, data.tube, data.radialSegments, data.tubularSegments, data.arc
 					data.radius, data.tube, data.radialSegments, data.tubularSegments, data.arc
 				)
 				)
-			)
+			);
 
 
 		}
 		}
 
 
-		var folder = gui.addFolder('THREE.TorusGeometry');
+		var folder = gui.addFolder( 'THREE.TorusGeometry' );
 
 
 		folder.add( data, 'radius', 1, 20 ).onChange( generateGeometry );
 		folder.add( data, 'radius', 1, 20 ).onChange( generateGeometry );
 		folder.add( data, 'tube', 0.1, 10 ).onChange( generateGeometry );
 		folder.add( data, 'tube', 0.1, 10 ).onChange( generateGeometry );
-		folder.add( data, 'radialSegments', 2, 30 ).step(1).onChange( generateGeometry );
-		folder.add( data, 'tubularSegments', 3, 200 ).step(1).onChange( generateGeometry );
+		folder.add( data, 'radialSegments', 2, 30 ).step( 1 ).onChange( generateGeometry );
+		folder.add( data, 'tubularSegments', 3, 200 ).step( 1 ).onChange( generateGeometry );
 		folder.add( data, 'arc', 0.1, twoPi ).onChange( generateGeometry );
 		folder.add( data, 'arc', 0.1, twoPi ).onChange( generateGeometry );
 
 
 		generateGeometry();
 		generateGeometry();
@@ -506,39 +524,39 @@ var guis = {
 					data.radius, data.tube, data.radialSegments, data.tubularSegments,
 					data.radius, data.tube, data.radialSegments, data.tubularSegments,
 					data.p, data.q, data.heightScale
 					data.p, data.q, data.heightScale
 				)
 				)
-			)
+			);
 
 
 		}
 		}
 
 
-		var folder = gui.addFolder('THREE.TorusGeometry');
+		var folder = gui.addFolder( 'THREE.TorusGeometry' );
 
 
-		folder.add( data, 'radius', 1, 20 ).onChange( generateGeometry )
-		folder.add( data, 'tube', 0.1, 10 ).onChange( generateGeometry )
-		folder.add( data, 'radialSegments', 3, 300 ).step(1).onChange( generateGeometry )
-		folder.add( data, 'tubularSegments', 3, 20 ).step(1).onChange( generateGeometry )
-		folder.add( data, 'p', 1, 20 ).step(1).onChange( generateGeometry )
-		folder.add( data, 'q', 1, 20 ).step(1).onChange( generateGeometry )
-		folder.add( data, 'heightScale', 1, 20 ).onChange( generateGeometry )
+		folder.add( data, 'radius', 1, 20 ).onChange( generateGeometry );
+		folder.add( data, 'tube', 0.1, 10 ).onChange( generateGeometry );
+		folder.add( data, 'radialSegments', 3, 300 ).step( 1 ).onChange( generateGeometry );
+		folder.add( data, 'tubularSegments', 3, 20 ).step( 1 ).onChange( generateGeometry );
+		folder.add( data, 'p', 1, 20 ).step( 1 ).onChange( generateGeometry );
+		folder.add( data, 'q', 1, 20 ).step( 1 ).onChange( generateGeometry );
+		folder.add( data, 'heightScale', 1, 20 ).onChange( generateGeometry );
 
 
-		generateGeometry()
+		generateGeometry();
 
 
 	}
 	}
 
 
-}
+};
 
 
 function chooseFromHash ( mesh ) {
 function chooseFromHash ( mesh ) {
 
 
-	var selectedGeometry = window.location.hash.substring(1) || "TorusGeometry";
+	var selectedGeometry = window.location.hash.substring( 1 ) || "TorusGeometry";
 
 
 	if ( guis[ selectedGeometry ] !== undefined ) {
 	if ( guis[ selectedGeometry ] !== undefined ) {
 
 
-	    guis[ selectedGeometry ]( mesh );
+		guis[ selectedGeometry ]( mesh );
 
 
 	}
 	}
 
 
 	if ( selectedGeometry === 'TextGeometry' ) {
 	if ( selectedGeometry === 'TextGeometry' ) {
 
 
-	    return { fixed : true };
+		return { fixed : true };
 
 
 	}
 	}
 
 

+ 3 - 3
docs/scenes/js/material.js

@@ -104,10 +104,10 @@ var envMaps = (function () {
 		path + 'pz' + format, path + 'nz' + format
 		path + 'pz' + format, path + 'nz' + format
 	];
 	];
 
 
-	var reflectionCube = THREE.ImageUtils.loadTextureCube( urls );
+	var reflectionCube = new THREE.CubeTextureLoader().load( urls );
 	reflectionCube.format = THREE.RGBFormat;
 	reflectionCube.format = THREE.RGBFormat;
 
 
-	var refractionCube = THREE.ImageUtils.loadTextureCube( urls );
+	var refractionCube = new THREE.CubeTextureLoader().load( urls );
 	refractionCube.mapping = THREE.CubeRefractionMapping;
 	refractionCube.mapping = THREE.CubeRefractionMapping;
 	refractionCube.format = THREE.RGBFormat;
 	refractionCube.format = THREE.RGBFormat;
 
 
@@ -125,7 +125,7 @@ var textureMaps = (function () {
 
 
 	return {
 	return {
 		none : null,
 		none : null,
-		grass : THREE.ImageUtils.loadTexture( "../../examples/textures/terrain/grasslight-thin.jpg" )
+		grass : new THREE.TextureLoader().load( "../../examples/textures/terrain/grasslight-thin.jpg" )
 	};
 	};
 
 
 })();
 })();

+ 28 - 27
docs/scenes/material-browser.html

@@ -10,7 +10,7 @@
 				font-weight: normal;
 				font-weight: normal;
 				font-style: normal;
 				font-style: normal;
 			}
 			}
-			
+
 			body {
 			body {
 				margin:0;
 				margin:0;
 				font-family: 'inconsolata';
 				font-family: 'inconsolata';
@@ -18,9 +18,9 @@
 				line-height: 18px;
 				line-height: 18px;
 				overflow: hidden;
 				overflow: hidden;
 			}
 			}
-			
+
 			canvas { width: 100%; height: 100% }
 			canvas { width: 100%; height: 100% }
-			
+
 			#newWindow {
 			#newWindow {
 				display: block;
 				display: block;
 				position: absolute;
 				position: absolute;
@@ -31,23 +31,24 @@
 		</style>
 		</style>
 	</head>
 	</head>
 	<body>
 	<body>
-		
+
 		<a id='newWindow' href='./material-browser.html' target='_blank'>Open in New Window</a>
 		<a id='newWindow' href='./material-browser.html' target='_blank'>Open in New Window</a>
-		
+
 		<script src="../../build/three.min.js"></script>
 		<script src="../../build/three.min.js"></script>
 		<script src='../../examples/js/libs/dat.gui.min.js'></script>
 		<script src='../../examples/js/libs/dat.gui.min.js'></script>
 		<script src='js/material.js'></script>
 		<script src='js/material.js'></script>
-		
+
 		<script>
 		<script>
-			
+
 			document.getElementById('newWindow').href += window.location.hash;
 			document.getElementById('newWindow').href += window.location.hash;
-			
+
 			var gui = new dat.GUI();
 			var gui = new dat.GUI();
 			var scene = new THREE.Scene();
 			var scene = new THREE.Scene();
 			var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 50 );
 			var camera = new THREE.PerspectiveCamera( 75, window.innerWidth/window.innerHeight, 0.1, 50 );
 			camera.position.z = 30;
 			camera.position.z = 30;
-			
-			var renderer = new THREE.WebGLRenderer();
+
+			var renderer = new THREE.WebGLRenderer( { antialias: true } );
+			renderer.setPixelRatio( window.devicePixelRatio );
 			renderer.setSize( window.innerWidth, window.innerHeight );
 			renderer.setSize( window.innerWidth, window.innerHeight );
 			document.body.appendChild( renderer.domElement );
 			document.body.appendChild( renderer.domElement );
 
 
@@ -58,7 +59,7 @@
 			lights[0] = new THREE.PointLight( 0xffffff, 1, 0 );
 			lights[0] = new THREE.PointLight( 0xffffff, 1, 0 );
 			lights[1] = new THREE.PointLight( 0xffffff, 1, 0 );
 			lights[1] = new THREE.PointLight( 0xffffff, 1, 0 );
 			lights[2] = new THREE.PointLight( 0xffffff, 1, 0 );
 			lights[2] = new THREE.PointLight( 0xffffff, 1, 0 );
-			
+
 			lights[0].position.set( 0, 200, 0 );
 			lights[0].position.set( 0, 200, 0 );
 			lights[1].position.set( 100, 200, 100 );
 			lights[1].position.set( 100, 200, 100 );
 			lights[2].position.set( -100, -200, -100 );
 			lights[2].position.set( -100, -200, -100 );
@@ -71,54 +72,54 @@
 
 
 			var geometry = new THREE.TorusKnotGeometry( 10, 3, 100, 16 );
 			var geometry = new THREE.TorusKnotGeometry( 10, 3, 100, 16 );
 			var mesh = new THREE.Mesh( geometry );
 			var mesh = new THREE.Mesh( geometry );
-			
+
 			generateVertexColors( geometry );
 			generateVertexColors( geometry );
-			
+
 			mesh.material = chooseFromHash( gui, mesh, geometry );
 			mesh.material = chooseFromHash( gui, mesh, geometry );
 
 
 			generateMorphTargets( mesh, geometry );
 			generateMorphTargets( mesh, geometry );
 
 
 			scene.add( mesh );
 			scene.add( mesh );
-			
+
 			var prevFog = false;
 			var prevFog = false;
-			
+
 			var render = function () {
 			var render = function () {
-				
+
 				requestAnimationFrame( render );
 				requestAnimationFrame( render );
 
 
 				var time = Date.now() * 0.001;
 				var time = Date.now() * 0.001;
 
 
 				mesh.rotation.x += 0.005;
 				mesh.rotation.x += 0.005;
 				mesh.rotation.y += 0.005;
 				mesh.rotation.y += 0.005;
-				
+
 				if ( prevFog !== scene.fog ) {
 				if ( prevFog !== scene.fog ) {
-					
+
 					mesh.material.needsUpdate = true;
 					mesh.material.needsUpdate = true;
 					prevFog = scene.fog;
 					prevFog = scene.fog;
-					
+
 				}
 				}
-				
+
 				if ( mesh.morphTargetInfluences ) {
 				if ( mesh.morphTargetInfluences ) {
-					
+
 					mesh.morphTargetInfluences[ 0 ] = ( 1 + Math.sin( 4 * time ) ) / 2;
 					mesh.morphTargetInfluences[ 0 ] = ( 1 + Math.sin( 4 * time ) ) / 2;
-					
+
 				}
 				}
 
 
 				renderer.render( scene, camera );
 				renderer.render( scene, camera );
-				
+
 			};
 			};
-			
+
 			window.addEventListener( 'resize', function () {
 			window.addEventListener( 'resize', function () {
-				
+
 				camera.aspect = window.innerWidth / window.innerHeight;
 				camera.aspect = window.innerWidth / window.innerHeight;
 				camera.updateProjectionMatrix();
 				camera.updateProjectionMatrix();
 
 
 				renderer.setSize( window.innerWidth, window.innerHeight );
 				renderer.setSize( window.innerWidth, window.innerHeight );
-				
+
 			}, false );
 			}, false );
 
 
 			render();
 			render();
-			
+
 		</script>
 		</script>
 	</body>
 	</body>
 </html>
 </html>

+ 49 - 28
editor/css/dark.css

@@ -29,9 +29,8 @@
 
 
 input.Number {
 input.Number {
 	color: #2A75B7!important;
 	color: #2A75B7!important;
-	font-size: 12px;							/** TODO: Use of !imporant is not ideal **/
-	background-color: transparent!important;	/* For now this is a quick fix a rendering issue due to inherited background */
-	border: 1px solid transparent;
+	font-size: 12px;
+	border: 0px;
 	padding: 2px;
 	padding: 2px;
 	cursor: col-resize;
 	cursor: col-resize;
 }
 }
@@ -39,20 +38,20 @@ input.Number {
 #viewport {
 #viewport {
 	position: absolute;
 	position: absolute;
 	top: 32px;
 	top: 32px;
-	left: 0px;
+	left: 0;
 	right: 300px;
 	right: 300px;
 	bottom: 32px;
 	bottom: 32px;
 }
 }
 
 
 	#viewport #info {
 	#viewport #info {
-		text-shadow: 1px 1px 0px rgba(0,0,0,0.25);
+		text-shadow: 1px 1px 0 rgba(0,0,0,0.25);
 		pointer-events: none;
 		pointer-events: none;
 	}
 	}
 
 
 #script {
 #script {
 	position: absolute;
 	position: absolute;
 	top: 32px;
 	top: 32px;
-	left: 0px;
+	left: 0;
 	right: 300px;
 	right: 300px;
 	bottom: 32px;
 	bottom: 32px;
 	opacity: 0.9;
 	opacity: 0.9;
@@ -61,7 +60,7 @@ input.Number {
 #player {
 #player {
 	position: absolute;
 	position: absolute;
 	top: 32px;
 	top: 32px;
-	left: 0px;
+	left: 0;
 	right: 300px;
 	right: 300px;
 	bottom: 32px;
 	bottom: 32px;
 }
 }
@@ -71,10 +70,10 @@ input.Number {
 	width: 100%;
 	width: 100%;
 	height: 32px;
 	height: 32px;
 	background: #111;
 	background: #111;
-	padding: 0px;
-	margin: 0px;
-	right: 0px;
-	top: 0px;
+	padding: 0;
+	margin: 0;
+	right: 0;
+	top: 0;
 }
 }
 
 
 	#menubar .menu {
 	#menubar .menu {
@@ -86,21 +85,21 @@ input.Number {
 	#menubar .menu.right {
 	#menubar .menu.right {
 		float: right;
 		float: right;
 		cursor: auto;
 		cursor: auto;
-		padding-right: 0px;
+		padding-right: 0;
 		text-align: right;
 		text-align: right;
 	}
 	}
 
 
 		#menubar .menu .title {
 		#menubar .menu .title {
 			display: inline-block;
 			display: inline-block;
 			color: #888;
 			color: #888;
-			margin: 0px;
+			margin: 0;
 			padding: 8px;
 			padding: 8px;
 		}
 		}
 
 
 		#menubar .menu .options {
 		#menubar .menu .options {
 			position: absolute;
 			position: absolute;
 			display: none;
 			display: none;
-			padding: 5px 0px;
+			padding: 5px 0;
 			background: #111;
 			background: #111;
 			width: 150px;
 			width: 150px;
 		}
 		}
@@ -110,14 +109,14 @@ input.Number {
 		}
 		}
 
 
 			#menubar .menu .options hr {
 			#menubar .menu .options hr {
-				border-color: #333;
+				border-color: #222;
 			}
 			}
 
 
 			#menubar .menu .options .option {
 			#menubar .menu .options .option {
 				color: #888;
 				color: #888;
 				background-color: transparent;
 				background-color: transparent;
 				padding: 5px 10px;
 				padding: 5px 10px;
-				margin: 0px !important;
+				margin: 0 !important;
 			}
 			}
 
 
 				#menubar .menu .options .option:hover {
 				#menubar .menu .options .option:hover {
@@ -129,11 +128,18 @@ input.Number {
 					background: transparent;
 					background: transparent;
 				}
 				}
 
 
+		#menubar .menu .options .inactive {
+			color: #444;
+			background-color: transparent;
+			padding: 5px 10px;
+			margin: 0 !important;
+		}
+
 #sidebar {
 #sidebar {
 	position: absolute;
 	position: absolute;
-	right: 0px;
+	right: 0;
 	top: 32px;
 	top: 32px;
-	bottom: 0px;
+	bottom: 0;
 	width: 300px;
 	width: 300px;
 	background-color: #111;
 	background-color: #111;
 	overflow: auto;
 	overflow: auto;
@@ -152,30 +158,45 @@ input.Number {
 	}
 	}
 
 
 	#sidebar .Panel {
 	#sidebar .Panel {
-		margin-bottom: 10px;
+		color: #888;
+		padding: 10px;
+		border-top: 1px solid #222;
 	}
 	}
 
 
 	#sidebar .Panel.collapsed {
 	#sidebar .Panel.collapsed {
-		margin-bottom: 0px;
+		margin-bottom: 0;
 	}
 	}
 
 
-	#sidebar > .Panel {
-		color: #888;
-		padding: 10px;
-		border-top: 1px solid #333;
+	#sidebar .Panel.Material canvas {
+		border: solid 1px #5A5A5A;
 	}
 	}
 
 
-	#sidebar .Panel.Material canvas {
+	#sidebar .Row {
+		min-height: 20px;
+		margin-bottom: 10px;
+	}
 
 
-		border: solid 1px #5A5A5A;
+#tabs {
+	background-color: #1b1b1b;
+	border-top: 1px solid #222;
+}
 
 
+	#tabs span {
+		color: #555;
+		border-right: 1px solid #222;
+		padding: 10px;
+	}
+
+	#tabs span.selected {
+		color: #888;
+		background-color: #111;
 	}
 	}
 
 
 #toolbar {
 #toolbar {
 	position: absolute;
 	position: absolute;
-	left: 0px;
+	left: 0;
 	right: 300px;
 	right: 300px;
-	bottom: 0px;
+	bottom: 0;
 	height: 32px;
 	height: 32px;
 	background-color: #111;
 	background-color: #111;
 	color: #333;
 	color: #333;

+ 47 - 24
editor/css/light.css

@@ -22,9 +22,8 @@
 
 
 input.Number {
 input.Number {
 	color: #0080f0!important;
 	color: #0080f0!important;
-	font-size: 12px;							/** TODO: Use of !imporant is not ideal **/
-	background-color: transparent!important;	/* For now this is a quick fix a rendering issue due to inherited background */
-	border: 1px solid transparent;
+	font-size: 12px;
+	border: 0px;
 	padding: 2px;
 	padding: 2px;
 	cursor: col-resize;
 	cursor: col-resize;
 }
 }
@@ -32,20 +31,20 @@ input.Number {
 #viewport {
 #viewport {
 	position: absolute;
 	position: absolute;
 	top: 32px;
 	top: 32px;
-	left: 0px;
+	left: 0;
 	right: 300px;
 	right: 300px;
 	bottom: 32px;
 	bottom: 32px;
 }
 }
 
 
 	#viewport #info {
 	#viewport #info {
-		text-shadow: 1px 1px 0px rgba(0,0,0,0.25);
+		text-shadow: 1px 1px 0 rgba(0,0,0,0.25);
 		pointer-events: none;
 		pointer-events: none;
 	}
 	}
 
 
 #script {
 #script {
 	position: absolute;
 	position: absolute;
 	top: 32px;
 	top: 32px;
-	left: 0px;
+	left: 0;
 	right: 300px;
 	right: 300px;
 	bottom: 32px;
 	bottom: 32px;
 	opacity: 0.9;
 	opacity: 0.9;
@@ -54,7 +53,7 @@ input.Number {
 #player {
 #player {
 	position: absolute;
 	position: absolute;
 	top: 32px;
 	top: 32px;
-	left: 0px;
+	left: 0;
 	right: 300px;
 	right: 300px;
 	bottom: 32px;
 	bottom: 32px;
 }
 }
@@ -64,10 +63,10 @@ input.Number {
 	width: 100%;
 	width: 100%;
 	height: 32px;
 	height: 32px;
 	background: #eee;
 	background: #eee;
-	padding: 0px;
-	margin: 0px;
-	right: 0px;
-	top: 0px;
+	padding: 0;
+	margin: 0;
+	right: 0;
+	top: 0;
 }
 }
 
 
 	#menubar .menu {
 	#menubar .menu {
@@ -79,21 +78,21 @@ input.Number {
 	#menubar .menu.right {
 	#menubar .menu.right {
 		float: right;
 		float: right;
 		cursor: auto;
 		cursor: auto;
-		padding-right: 0px;
+		padding-right: 0;
 		text-align: right;
 		text-align: right;
 	}
 	}
 
 
 		#menubar .menu .title {
 		#menubar .menu .title {
 			display: inline-block;
 			display: inline-block;
 			color: #888;
 			color: #888;
-			margin: 0px;
+			margin: 0;
 			padding: 8px;
 			padding: 8px;
 		}
 		}
 
 
 		#menubar .menu .options {
 		#menubar .menu .options {
 			position: absolute;
 			position: absolute;
 			display: none;
 			display: none;
-			padding: 5px 0px;
+			padding: 5px 0;
 			background: #eee;
 			background: #eee;
 			width: 150px;
 			width: 150px;
 		}
 		}
@@ -110,7 +109,7 @@ input.Number {
 				color: #666;
 				color: #666;
 				background-color: transparent;
 				background-color: transparent;
 				padding: 5px 10px;
 				padding: 5px 10px;
-				margin: 0px !important;
+				margin: 0 !important;
 			}
 			}
 
 
 				#menubar .menu .options .option:hover {
 				#menubar .menu .options .option:hover {
@@ -123,11 +122,18 @@ input.Number {
 					background: transparent;
 					background: transparent;
 				}
 				}
 
 
+		#menubar .menu .options .inactive {
+			color: #bbb;
+			background-color: transparent;
+			padding: 5px 10px;
+			margin: 0 !important;
+		}
+
 #sidebar {
 #sidebar {
 	position: absolute;
 	position: absolute;
-	right: 0px;
+	right: 0;
 	top: 32px;
 	top: 32px;
-	bottom: 0px;
+	bottom: 0;
 	width: 300px;
 	width: 300px;
 	background: #eee;
 	background: #eee;
 	overflow: auto;
 	overflow: auto;
@@ -145,24 +151,41 @@ input.Number {
 	}
 	}
 
 
 	#sidebar .Panel {
 	#sidebar .Panel {
-		margin-bottom: 10px;
+		color: #888;
+		padding: 10px;
+		border-top: 1px solid #ccc;
 	}
 	}
 
 
 	#sidebar .Panel.collapsed {
 	#sidebar .Panel.collapsed {
-		margin-bottom: 0px;
+		margin-bottom: 0;
 	}
 	}
 
 
-	#sidebar > .Panel {
-		color: #888;
+	#sidebar .Row {
+		min-height: 20px;
+		margin-bottom: 10px;
+	}
+
+#tabs {
+	background-color: #ddd;
+	border-top: 1px solid #ccc;
+}
+
+	#tabs span {
+		color: #aaa;
+		border-right: 1px solid #ccc;
 		padding: 10px;
 		padding: 10px;
-		border-top: 1px solid #ccc;
+	}
+
+	#tabs span.selected {
+		color: #888;
+		background-color: #eee;
 	}
 	}
 
 
 #toolbar {
 #toolbar {
 	position: absolute;
 	position: absolute;
-	left: 0px;
+	left: 0;
 	right: 300px;
 	right: 300px;
-	bottom: 0px;
+	bottom: 0;
 	height: 32px;
 	height: 32px;
 	background: #eee;
 	background: #eee;
 	color: #333;
 	color: #333;

+ 29 - 21
editor/css/main.css

@@ -6,7 +6,7 @@ body {
 }
 }
 
 
 hr {
 hr {
-	border: 0px;
+	border: 0;
 	border-top: 1px solid #ccc;
 	border-top: 1px solid #ccc;
 }
 }
 
 
@@ -42,14 +42,14 @@ textarea, input { outline: none; } /* osx */
 }
 }
 
 
 	.Panel.Collapsible .Static {
 	.Panel.Collapsible .Static {
-		margin: 0px;
+		margin: 0;
 	}
 	}
 
 
 	.Panel.Collapsible .Static .Button {
 	.Panel.Collapsible .Static .Button {
 		float: left;
 		float: left;
 		margin-right: 6px;
 		margin-right: 6px;
-		width: 0px;
-		height: 0px;
+		width: 0;
+		height: 0;
 		border: 6px solid transparent;
 		border: 6px solid transparent;
 	}
 	}
 
 
@@ -88,71 +88,79 @@ textarea, input { outline: none; } /* osx */
 
 
 		color: #f00;
 		color: #f00;
 		text-align: right;
 		text-align: right;
-		padding: 0px 20px;
+		padding: 0 20px;
 
 
 	}
 	}
 
 
-/* scene types */
+/* outliner */
 
 
-.type {
+#outliner .type {
 	position:relative;
 	position:relative;
 	top:-2px;
 	top:-2px;
-	padding: 0px 2px;
+	padding: 0 2px;
 	color: #ddd;
 	color: #ddd;
 }
 }
-.type:after {
+
+#outliner .type:after {
 	content: '■';
 	content: '■';
 }
 }
 
 
-.Scene {
+#outliner .Scene {
 	color: #ccccff;
 	color: #ccccff;
 }
 }
 
 
-.Object3D {
+#outliner .Object3D {
 	color: #aaaaee;
 	color: #aaaaee;
 }
 }
 
 
-.Mesh {
+#outliner .Mesh {
 	color: #8888ee;
 	color: #8888ee;
 }
 }
 
 
-.Line {
+#outliner .Line {
 	color: #88ee88;
 	color: #88ee88;
 }
 }
 
 
-.LineSegments {
+#outliner .LineSegments {
 	color: #88ee88;
 	color: #88ee88;
 }
 }
 
 
-.Points {
+#outliner .Points {
 	color: #ee8888;
 	color: #ee8888;
 }
 }
 
 
 /* */
 /* */
 
 
-.PointLight {
+#outliner .PointLight {
 	color: #dddd00;
 	color: #dddd00;
 }
 }
 
 
 /* */
 /* */
 
 
-.Geometry {
+#outliner .Geometry {
 	color: #88ff88;
 	color: #88ff88;
 }
 }
 
 
-.BoxGeometry {
+#outliner .BoxGeometry {
 	color: #bbeebb;
 	color: #bbeebb;
 }
 }
-.TorusGeometry {
+
+#outliner .TorusGeometry {
 	color: #aaeeaa;
 	color: #aaeeaa;
 }
 }
 
 
 /* */
 /* */
 
 
-.Material {
+#outliner .Material {
 	color: #ff8888;
 	color: #ff8888;
 }
 }
 
 
-.MeshPhongMaterial {
+#outliner .MeshPhongMaterial {
 	color: #ffaa88;
 	color: #ffaa88;
 }
 }
+
+/* */
+
+#outliner .Script:after {
+	content: '{...}' /* ❮/❯ */
+}

+ 132 - 0
editor/docs/Implementing additional commands for undo-redo.md

@@ -0,0 +1,132 @@
+How to implement additional commands for undo/redo functionality?
+===
+
+### Basics ###
+
+After evaluating different design patterns for undo/redo we decided to use the [command-pattern](http://en.wikipedia.org/wiki/Command_pattern) for implementing undo/redo functionality in the three.js-editor.
+
+This means that every action is encapsulated in a command-object which contains all the relevant information to restore the previous state.
+
+In our implementation we store the old and the new state separately (we don't store the complete state but rather the attribute and value which has changed).
+It would also be possible to only store the difference between the old and the new state.
+
+**Before implementing your own command you should look if you can't reuse one of the already existing ones.**
+
+For numbers, strings or booleans the Set...ValueCommand-commands can be used.
+Then there are separate commands for:
+- setting a color property (THREE.Color)
+- setting maps (THREE.Texture)
+- setting geometries
+- setting materials
+- setting position, rotation and scale
+
+### Template for new commands ###
+
+Every command needs a constructor. In the constructor
+
+```javascript
+	
+var DoSomethingCommand = function () {
+
+	Command.call( this ); // Required: Call default constructor
+
+	this.type = 'DoSomethingCommand';            // Required: has to match the object-name!
+	this.name = 'Set/Do/Update Something'; // Required: description of the command, used in Sidebar.History
+
+	// TODO: store all the relevant information needed to 
+	// restore the old and the new state
+
+};
+```
+
+And as part of the prototype you need to implement four functions
+- **execute:** which is also used for redo
+- **undo:** which reverts the changes made by 'execute'
+- **toJSON:** which serializes the command so that the undo/redo-history can be preserved across a browser refresh
+- **fromJSON:** which deserializes the command
+
+```javascript
+DoSomethingCommand.prototype = {
+
+	execute: function () {
+
+		// TODO: apply changes to 'object' to reach the new state 
+
+	},
+
+	undo: function () {
+
+		// TODO: restore 'object' to old state 
+
+	},
+
+	toJSON: function () {
+
+		var output = Command.prototype.toJSON.call( this ); // Required: Call 'toJSON'-method of prototype 'Command'
+
+		// TODO: serialize all the necessary information as part of 'output' (JSON-format)
+		// so that it can be restored in 'fromJSON'
+
+		return output;
+
+	},
+
+	fromJSON: function ( json ) {
+
+		Command.prototype.fromJSON.call( this, json ); // Required: Call 'fromJSON'-method of prototype 'Command'
+
+		// TODO: restore command from json
+
+	}
+
+};
+
+```
+
+### Executing a command ###
+
+To execute a command we need an instance of the main editor-object. The editor-object functions as the only entry point through which all commands have to go to be added as part of the undo/redo-history.
+On **editor** we then call **.execute(...)*** with the new command-object which in turn calls **history.execute(...)** and adds the command to the undo-stack.
+
+```javascript
+
+editor.execute( new DoSomethingCommand() );
+
+```
+
+### Updatable commands ###
+
+Some commands are also **updatable**. By default a command is not updatable. Making a command updatable means that you
+have to implement a fifth function 'update' as part of the prototype. In it only the 'new' state gets updated while the old one stays the same.
+
+Here as an example is the update-function of **SetColorCommand**:
+
+```javascript
+update: function ( cmd ) {
+
+	this.newValue = cmd.newValue;
+
+},
+
+```
+
+#### List of updatable commands
+
+- SetColorCommand
+- SetGeometryCommand
+- SetMaterialColorCommand
+- SetMaterialValueCommand
+- SetPositionCommand
+- SetRotationCommand
+- SetScaleCommand
+- SetValueCommand
+- SetScriptValueCommand
+
+The idea behind 'updatable commands' is that two commands of the same type which occur
+within a short period of time should be merged into one.
+**For example:** Dragging with your mouse over the x-position field in the sidebar
+leads to hundreds of minor changes to the x-position.
+The user expectation is not to undo every single change that happened while he dragged
+the mouse cursor but rather to go back to the position before he started to drag his mouse.
+
+When editing a script the changes are also merged into one undo-step.

+ 94 - 0
editor/docs/Writing unit tests for undo-redo commands.md

@@ -0,0 +1,94 @@
+Writing unit tests for undo-redo commands
+===
+
+### Overview ###
+
+Writing unit tests for undo/redo commands is easy.
+The main idea to simulate a scene, execute actions and perform undo and redo.
+Following steps are required.
+
+1. Create a new unit test file
+2. Include the new command and the unit test file in the editor's test suite
+3. Write the test
+4. Execute the test
+
+Each of the listed steps will now be described in detail.
+
+### 1. Create a new unit test file ###
+
+Create a new file in path `test/unit/editor/TestDoSomethingCommand.js`.
+
+### 2. Include the new command in the editor test suite ###
+
+Navigate to the editor test suite `test/unit/unittests_editor.html` and open it.
+Within the file, go to the `<!-- command object classes -->` and include the new command:
+
+```html
+// <!-- command object classes -->
+//...
+<script src="../../editor/js/commands/AddScriptCommand.js"></script>
+<script src="../../editor/js/commands/DoSomethingCommand.js"></script>         // add this line
+<script src="../../editor/js/commands/MoveObjectCommand.js"></script>
+//...
+```
+
+It is recommended to keep the script inclusions in alphabetical order, if possible.
+
+Next, in the same file, go to `<!-- Undo-Redo tests -->` and include the test file for the new command:
+
+```html
+// <!-- Undo-Redo tests -->
+//...
+<script src="editor/TestAddScriptCommand.js"></script>
+<script src="editor/TestDoSomethingCommand.js"></script>              // add this line
+<script src="editor/TestMoveObjectCommand.js"></script>
+//...
+```
+
+Again, keeping the alphabetical order is recommended.
+
+### 3. Write the test ###
+
+#### Template ####
+
+Open the unit test file `test/unit/editor/TestDoSomethingCommand.js` and paste following code:
+
+```javascript
+module( "DoSomethingCommand" );
+
+test("Test DoSomethingCommand (Undo and Redo)", function() {
+
+    var editor = new Editor();
+
+    var box = aBox( 'Name your box' );
+
+    // other available objects from "CommonUtilities.js"
+    // var sphere = aSphere( 'Name your sphere' );
+    // var pointLight = aPointLight( 'Name your pointLight' );
+    // var perspectiveCamera = aPerspectiveCamera( 'Name your perspectiveCamera' );
+
+    // in most cases you'll need to add the object to work with
+    editor.execute( new AddObjectCommand( box ) );
+
+
+    // your test begins here...
+
+
+} );
+```
+
+The predefined code is just meant to ease the development, you do not have to stick with it.
+However, the test should cover at least one `editor.execute()`, one `editor.undo()` and one `editor.redo()` call.
+
+Best practice is to call `editor.execute( new DoSomethingCommand( {custom parameters} ) )` **twice**. Since you'll have to do one undo (go one step back), it is recommended to have a custom state for comparison. Try to avoid assertions `ok()` against default values.
+
+#### Assertions ####
+After performing `editor.execute()` twice, you can do your first assertion to check whether the executes are done correctly.
+
+Next, you perform `editor.undo()` and check if the last action was undone.
+
+Finally, perform `editor.redo()` and verify if the values are as expected.
+
+### 4. Execute the test ###
+
+Open the editor's unit test suite `test/unit/unittests_editor.html` in your browser and check the results from the test framework.

+ 55 - 14
editor/index.html

@@ -33,10 +33,12 @@
 		<script src="../examples/js/loaders/AMFLoader.js"></script>
 		<script src="../examples/js/loaders/AMFLoader.js"></script>
 		<script src="../examples/js/loaders/AWDLoader.js"></script>
 		<script src="../examples/js/loaders/AWDLoader.js"></script>
 		<script src="../examples/js/loaders/BabylonLoader.js"></script>
 		<script src="../examples/js/loaders/BabylonLoader.js"></script>
-		<script src="../examples/js/loaders/ColladaLoader.js"></script>
+		<script src="../examples/js/loaders/ColladaLoader2.js"></script>
+		<script src="../examples/js/loaders/FBXLoader.js"></script>
 		<script src="../examples/js/loaders/KMZLoader.js"></script>
 		<script src="../examples/js/loaders/KMZLoader.js"></script>
 		<script src="../examples/js/loaders/MD2Loader.js"></script>
 		<script src="../examples/js/loaders/MD2Loader.js"></script>
 		<script src="../examples/js/loaders/OBJLoader.js"></script>
 		<script src="../examples/js/loaders/OBJLoader.js"></script>
+		<script src="../examples/js/loaders/PlayCanvasLoader.js"></script>
 		<script src="../examples/js/loaders/PLYLoader.js"></script>
 		<script src="../examples/js/loaders/PLYLoader.js"></script>
 		<script src="../examples/js/loaders/STLLoader.js"></script>
 		<script src="../examples/js/loaders/STLLoader.js"></script>
 		<script src="../examples/js/loaders/UTF8Loader.js"></script>
 		<script src="../examples/js/loaders/UTF8Loader.js"></script>
@@ -108,15 +110,15 @@
 		<script src="js/Menubar.Edit.js"></script>
 		<script src="js/Menubar.Edit.js"></script>
 		<script src="js/Menubar.Add.js"></script>
 		<script src="js/Menubar.Add.js"></script>
 		<script src="js/Menubar.Play.js"></script>
 		<script src="js/Menubar.Play.js"></script>
-		<script src="js/Menubar.View.js"></script>
 		<script src="js/Menubar.Examples.js"></script>
 		<script src="js/Menubar.Examples.js"></script>
 		<script src="js/Menubar.Help.js"></script>
 		<script src="js/Menubar.Help.js"></script>
 		<script src="js/Menubar.Status.js"></script>
 		<script src="js/Menubar.Status.js"></script>
 		<script src="js/Sidebar.js"></script>
 		<script src="js/Sidebar.js"></script>
-		<script src="js/Sidebar.Project.js"></script>
 		<script src="js/Sidebar.Scene.js"></script>
 		<script src="js/Sidebar.Scene.js"></script>
-		<script src="js/Sidebar.Object3D.js"></script>
-		<script src="js/Sidebar.Animation.js"></script>
+		<script src="js/Sidebar.Project.js"></script>
+		<script src="js/Sidebar.Settings.js"></script>
+		<script src="js/Sidebar.Properties.js"></script>
+		<script src="js/Sidebar.Object.js"></script>
 		<script src="js/Sidebar.Geometry.js"></script>
 		<script src="js/Sidebar.Geometry.js"></script>
 		<script src="js/Sidebar.Geometry.Geometry.js"></script>
 		<script src="js/Sidebar.Geometry.Geometry.js"></script>
 		<script src="js/Sidebar.Geometry.BufferGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.BufferGeometry.js"></script>
@@ -131,11 +133,37 @@
 		<script src="js/Sidebar.Geometry.TorusKnotGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.TorusKnotGeometry.js"></script>
 		<script src="../examples/js/geometries/TeapotBufferGeometry.js"></script>
 		<script src="../examples/js/geometries/TeapotBufferGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.TeapotBufferGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.TeapotBufferGeometry.js"></script>
+		<script src="js/Sidebar.Geometry.LatheGeometry.js"></script>
 		<script src="js/Sidebar.Material.js"></script>
 		<script src="js/Sidebar.Material.js"></script>
+		<script src="js/Sidebar.Animation.js"></script>
 		<script src="js/Sidebar.Script.js"></script>
 		<script src="js/Sidebar.Script.js"></script>
+		<script src="js/Sidebar.History.js"></script>
 		<script src="js/Toolbar.js"></script>
 		<script src="js/Toolbar.js"></script>
 		<script src="js/Viewport.js"></script>
 		<script src="js/Viewport.js"></script>
 		<script src="js/Viewport.Info.js"></script>
 		<script src="js/Viewport.Info.js"></script>
+		<script src="js/Command.js"></script>
+		<script src="js/commands/AddObjectCommand.js"></script>
+		<script src="js/commands/RemoveObjectCommand.js"></script>
+		<script src="js/commands/MoveObjectCommand.js"></script>
+		<script src="js/commands/SetPositionCommand.js"></script>
+		<script src="js/commands/SetRotationCommand.js"></script>
+		<script src="js/commands/SetScaleCommand.js"></script>
+		<script src="js/commands/SetValueCommand.js"></script>
+		<script src="js/commands/SetUuidCommand.js"></script>
+		<script src="js/commands/SetColorCommand.js"></script>
+		<script src="js/commands/SetGeometryCommand.js"></script>
+		<script src="js/commands/SetGeometryValueCommand.js"></script>
+		<script src="js/commands/MultiCmdsCommand.js"></script>
+		<script src="js/commands/AddScriptCommand.js"></script>
+		<script src="js/commands/RemoveScriptCommand.js"></script>
+		<script src="js/commands/SetScriptValueCommand.js"></script>
+		<script src="js/commands/SetMaterialCommand.js"></script>
+		<script src="js/commands/SetMaterialValueCommand.js"></script>
+		<script src="js/commands/SetMaterialColorCommand.js"></script>
+		<script src="js/commands/SetMaterialMapCommand.js"></script>
+		<script src="js/commands/SetSceneCommand.js"></script>
+
+		<!-- <script type="text/javascript" src="https://www.dropbox.com/static/api/2/dropins.js" id="dropboxjs" data-app-key="qyqgfqd9j8z890t"></script> -->
 
 
 		<script>
 		<script>
 
 
@@ -153,12 +181,12 @@
 			var viewport = new Viewport( editor );
 			var viewport = new Viewport( editor );
 			document.body.appendChild( viewport.dom );
 			document.body.appendChild( viewport.dom );
 
 
-			var player = new Player( editor );
-			document.body.appendChild( player.dom );
-
 			var script = new Script( editor );
 			var script = new Script( editor );
 			document.body.appendChild( script.dom );
 			document.body.appendChild( script.dom );
 
 
+			var player = new Player( editor );
+			document.body.appendChild( player.dom );
+
 			var toolbar = new Toolbar( editor );
 			var toolbar = new Toolbar( editor );
 			document.body.appendChild( toolbar.dom );
 			document.body.appendChild( toolbar.dom );
 
 
@@ -234,6 +262,7 @@
 				signals.materialChanged.add( saveState );
 				signals.materialChanged.add( saveState );
 				signals.sceneGraphChanged.add( saveState );
 				signals.sceneGraphChanged.add( saveState );
 				signals.scriptChanged.add( saveState );
 				signals.scriptChanged.add( saveState );
+				signals.historyChanged.add( saveState );
 
 
 				signals.showModal.add( function ( content ) {
 				signals.showModal.add( function ( content ) {
 
 
@@ -276,9 +305,21 @@
 						if ( confirm( 'Delete ' + object.name + '?' ) === false ) return;
 						if ( confirm( 'Delete ' + object.name + '?' ) === false ) return;
 
 
 						var parent = object.parent;
 						var parent = object.parent;
-						editor.removeObject( object );
-						editor.select( parent );
+						if ( parent !== null ) editor.execute( new RemoveObjectCommand( object ) );
+
+						break;
+
+					case 90: // Register Ctrl-Z for Undo, Ctrl-Shift-Z for Redo
+
+						if ( event.ctrlKey && event.shiftKey ) {
+
+							editor.redo();
+
+						} else if ( event.ctrlKey ) {
+
+							editor.undo();
 
 
+						}
 						break;
 						break;
 
 
 				}
 				}
@@ -297,13 +338,11 @@
 
 
 			//
 			//
 
 
-			var file = null;
 			var hash = window.location.hash;
 			var hash = window.location.hash;
 
 
-			if ( hash.substr( 1, 4 ) === 'app=' ) file = hash.substr( 5 );
-			if ( hash.substr( 1, 6 ) === 'scene=' ) file = hash.substr( 7 );
+			if ( hash.substr( 1, 5 ) === 'file=' ) {
 
 
-			if ( file !== null ) {
+				var file = hash.substr( 6 );
 
 
 				if ( confirm( 'Any unsaved data will be lost. Are you sure?' ) ) {
 				if ( confirm( 'Any unsaved data will be lost. Are you sure?' ) ) {
 
 
@@ -322,12 +361,14 @@
 
 
 			}
 			}
 
 
+			/*
 			window.addEventListener( 'message', function ( event ) {
 			window.addEventListener( 'message', function ( event ) {
 
 
 				editor.clear();
 				editor.clear();
 				editor.fromJSON( event.data );
 				editor.fromJSON( event.data );
 
 
 			}, false );
 			}, false );
+			*/
 
 
 		</script>
 		</script>
 	</body>
 	</body>

+ 47 - 0
editor/js/Command.js

@@ -0,0 +1,47 @@
+/**
+ * @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 = function ( editorRef ) {
+
+	this.id = - 1;
+	this.inMemory = false;
+	this.updatable = false;
+	this.type = '';
+	this.name = '';
+
+	if ( editorRef !== undefined ) {
+
+		Command.editor = editorRef;
+
+	}
+	this.editor = Command.editor;
+
+
+};
+
+Command.prototype.toJSON = function () {
+
+	var output = {};
+	output.type = this.type;
+	output.id = this.id;
+	output.name = this.name;
+	return output;
+
+};
+
+Command.prototype.fromJSON = function ( json ) {
+
+	this.inMemory = true;
+	this.type = json.type;
+	this.id = json.id;
+	this.name = json.name;
+
+};

+ 3 - 6
editor/js/Config.js

@@ -15,12 +15,9 @@ var Config = function () {
 		'project/renderer/shadows': true,
 		'project/renderer/shadows': true,
 		'project/vr': false,
 		'project/vr': false,
 
 
+		'settings/history': false,
+
 		'ui/sidebar/animation/collapsed': true,
 		'ui/sidebar/animation/collapsed': true,
-		'ui/sidebar/geometry/collapsed': true,
-		'ui/sidebar/material/collapsed': true,
-		'ui/sidebar/object3d/collapsed': false,
-		'ui/sidebar/project/collapsed': true,
-		'ui/sidebar/scene/collapsed': false,
 		'ui/sidebar/script/collapsed': true
 		'ui/sidebar/script/collapsed': true
 	};
 	};
 
 
@@ -68,6 +65,6 @@ var Config = function () {
 
 
 		}
 		}
 
 
-	}
+	};
 
 
 };
 };

+ 77 - 21
editor/js/Editor.js

@@ -6,6 +6,11 @@ var Editor = function () {
 
 
 	var SIGNALS = signals;
 	var SIGNALS = signals;
 
 
+	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() );
+
 	this.signals = {
 	this.signals = {
 
 
 		// script
 		// script
@@ -62,7 +67,10 @@ var Editor = function () {
 		fogParametersChanged: new SIGNALS.Signal(),
 		fogParametersChanged: new SIGNALS.Signal(),
 		windowResize: new SIGNALS.Signal(),
 		windowResize: new SIGNALS.Signal(),
 
 
-		showGridChanged: new SIGNALS.Signal()
+		showGridChanged: new SIGNALS.Signal(),
+		refreshSidebarObject3D: new SIGNALS.Signal(),
+		historyChanged: new SIGNALS.Signal(),
+		refreshScriptEditor: new SIGNALS.Signal()
 
 
 	};
 	};
 
 
@@ -71,10 +79,7 @@ var Editor = function () {
 	this.storage = new Storage();
 	this.storage = new Storage();
 	this.loader = new Loader( this );
 	this.loader = new Loader( this );
 
 
-	this.camera = new THREE.PerspectiveCamera( 50, 1, 1, 100000 );
-	this.camera.position.set( 500, 250, 500 );
-	this.camera.lookAt( new THREE.Vector3() );
-	this.camera.name = 'Camera';
+	this.camera = this.DEFAULT_CAMERA.clone();
 
 
 	this.scene = new THREE.Scene();
 	this.scene = new THREE.Scene();
 	this.scene.name = 'Scene';
 	this.scene.name = 'Scene';
@@ -233,7 +238,7 @@ Editor.prototype = {
 
 
 	addHelper: function () {
 	addHelper: function () {
 
 
-		var geometry = new THREE.SphereBufferGeometry( 20, 4, 2 );
+		var geometry = new THREE.SphereBufferGeometry( 2, 4, 2 );
 		var material = new THREE.MeshBasicMaterial( { color: 0xff0000, visible: false } );
 		var material = new THREE.MeshBasicMaterial( { color: 0xff0000, visible: false } );
 
 
 		return function ( object ) {
 		return function ( object ) {
@@ -242,23 +247,23 @@ Editor.prototype = {
 
 
 			if ( object instanceof THREE.Camera ) {
 			if ( object instanceof THREE.Camera ) {
 
 
-				helper = new THREE.CameraHelper( object, 10 );
+				helper = new THREE.CameraHelper( object, 1 );
 
 
 			} else if ( object instanceof THREE.PointLight ) {
 			} else if ( object instanceof THREE.PointLight ) {
 
 
-				helper = new THREE.PointLightHelper( object, 10 );
+				helper = new THREE.PointLightHelper( object, 1 );
 
 
 			} else if ( object instanceof THREE.DirectionalLight ) {
 			} else if ( object instanceof THREE.DirectionalLight ) {
 
 
-				helper = new THREE.DirectionalLightHelper( object, 20 );
+				helper = new THREE.DirectionalLightHelper( object, 1 );
 
 
 			} else if ( object instanceof THREE.SpotLight ) {
 			} else if ( object instanceof THREE.SpotLight ) {
 
 
-				helper = new THREE.SpotLightHelper( object, 10 );
+				helper = new THREE.SpotLightHelper( object, 1 );
 
 
 			} else if ( object instanceof THREE.HemisphereLight ) {
 			} else if ( object instanceof THREE.HemisphereLight ) {
 
 
-				helper = new THREE.HemisphereLightHelper( object, 10 );
+				helper = new THREE.HemisphereLightHelper( object, 1 );
 
 
 			} else if ( object instanceof THREE.SkinnedMesh ) {
 			} else if ( object instanceof THREE.SkinnedMesh ) {
 
 
@@ -405,8 +410,7 @@ Editor.prototype = {
 		this.history.clear();
 		this.history.clear();
 		this.storage.clear();
 		this.storage.clear();
 
 
-		this.camera.position.set( 500, 250, 500 );
-		this.camera.lookAt( new THREE.Vector3() );
+		this.camera.copy( this.DEFAULT_CAMERA );
 
 
 		var objects = this.scene.children;
 		var objects = this.scene.children;
 
 
@@ -444,33 +448,85 @@ Editor.prototype = {
 
 
 		// TODO: Clean this up somehow
 		// TODO: Clean this up somehow
 
 
+		if ( json.project !== undefined ) {
+
+			this.config.setKey( 'project/renderer/shadows', json.project.shadows );
+			this.config.setKey( 'project/vr', json.project.vr );
+
+		}
+
 		var camera = loader.parse( json.camera );
 		var camera = loader.parse( json.camera );
 
 
-		this.camera.position.copy( camera.position );
-		this.camera.rotation.copy( camera.rotation );
-		this.camera.aspect = camera.aspect;
-		this.camera.near = camera.near;
-		this.camera.far = camera.far;
+		this.camera.copy( camera );
+		this.camera.aspect = this.DEFAULT_CAMERA.aspect;
+		this.camera.updateProjectionMatrix();
 
 
-		this.setScene( loader.parse( json.scene ) );
+		this.history.fromJSON( json.history );
 		this.scripts = json.scripts;
 		this.scripts = json.scripts;
 
 
+		this.setScene( loader.parse( json.scene ) );
+
 	},
 	},
 
 
 	toJSON: function () {
 	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 {
 		return {
 
 
+			metadata: {},
 			project: {
 			project: {
 				shadows: this.config.getKey( 'project/renderer/shadows' ),
 				shadows: this.config.getKey( 'project/renderer/shadows' ),
 				vr: this.config.getKey( 'project/vr' )
 				vr: this.config.getKey( 'project/vr' )
 			},
 			},
 			camera: this.camera.toJSON(),
 			camera: this.camera.toJSON(),
 			scene: this.scene.toJSON(),
 			scene: this.scene.toJSON(),
-			scripts: this.scripts
+			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();
+
 	}
 	}
 
 
-}
+};

+ 277 - 35
editor/js/History.js

@@ -1,34 +1,36 @@
 /**
 /**
- * @author mrdoob / http://mrdoob.com/
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
  */
  */
 
 
-var History = function ( editor ) {
+History = function ( editor ) {
 
 
-	this.array = [];
-	this.arrayLength = -1;
+	this.editor = editor;
+	this.undos = [];
+	this.redos = [];
+	this.lastCmdTime = new Date();
+	this.idCounter = 0;
 
 
-	this.current = -1;
-	this.isRecording = true;
+	this.historyDisabled = false;
+	this.config = editor.config;
 
 
-	//
+	//Set editor-reference in Command
+
+	Command( editor );
+
+	// signals
 
 
 	var scope = this;
 	var scope = this;
-	var signals = editor.signals;
 
 
-	signals.objectAdded.add( function ( object ) {
+	this.editor.signals.startPlayer.add( function () {
 
 
-		if ( scope.isRecording === false ) return;
+		scope.historyDisabled = true;
 
 
-		scope.add(
-			function () {
-				editor.removeObject( object );
-				editor.select( null );
-			},
-			function () {
-				editor.addObject( object );
-				editor.select( object );
-			}
-		);
+	} );
+
+	this.editor.signals.stopPlayer.add( function () {
+
+		scope.historyDisabled = false;
 
 
 	} );
 	} );
 
 
@@ -36,45 +38,285 @@ var History = function ( editor ) {
 
 
 History.prototype = {
 History.prototype = {
 
 
-	add: function ( undo, redo ) {
+	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 {
 
 
-		this.current ++;
+			// the command is not updatable and is added as a new part of the history
 
 
-		this.array[ this.current ] = { undo: undo, redo: redo };
-		this.arrayLength = this.current;
+			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 () {
 	undo: function () {
 
 
-		if ( this.current < 0 ) return;
+		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 );
+
+			}
+
+		}
 
 
-		this.isRecording = false;
+		if ( cmd !== undefined ) {
 
 
-		this.array[ this.current -- ].undo();
+			cmd.undo();
+			this.redos.push( cmd );
+			this.editor.signals.historyChanged.dispatch( cmd );
 
 
-		this.isRecording = true;
+		}
+
+		return cmd;
 
 
 	},
 	},
 
 
 	redo: function () {
 	redo: function () {
 
 
-		if ( this.current === this.arrayLength ) return;
+		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 );
 
 
-		this.isRecording = false;
+			}
+
+		}
+
+		// 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;
 
 
-		this.array[ ++ this.current ].redo();
+		for ( var i = 0; i < json.undos.length; i ++ ) {
 
 
-		this.isRecording = true;
+			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 () {
 	clear: function () {
 
 
-		this.array = [];
-		this.arrayLength = -1;
+		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;
+
+				cmd = 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.current = -1;
+		this.goToState( id );
 
 
 	}
 	}
 
 

+ 148 - 119
editor/js/Loader.js

@@ -14,18 +14,25 @@ var Loader = function ( editor ) {
 		var filename = file.name;
 		var filename = file.name;
 		var extension = filename.split( '.' ).pop().toLowerCase();
 		var extension = filename.split( '.' ).pop().toLowerCase();
 
 
+		var reader = new FileReader();
+		reader.addEventListener( 'progress', function ( event ) {
+
+			var size = '(' + Math.floor( event.total / 1000 ).format() + ' KB)';
+			var progress = Math.floor( ( event.loaded / event.total ) * 100 ) + '%';
+			console.log( 'Loading', filename, size, progress );
+
+		} );
+
 		switch ( extension ) {
 		switch ( extension ) {
 
 
 			case 'amf':
 			case 'amf':
 
 
-				var reader = new FileReader();
 				reader.addEventListener( 'load', function ( event ) {
 				reader.addEventListener( 'load', function ( event ) {
 
 
 					var loader = new THREE.AMFLoader();
 					var loader = new THREE.AMFLoader();
 					var amfobject = loader.parse( event.target.result );
 					var amfobject = loader.parse( event.target.result );
 
 
-					editor.addObject( amfobject );
-					editor.select( amfobject );
+					editor.execute( new AddObjectCommand( amfobject ) );
 
 
 				}, false );
 				}, false );
 				reader.readAsArrayBuffer( file );
 				reader.readAsArrayBuffer( file );
@@ -34,13 +41,12 @@ var Loader = function ( editor ) {
 
 
 			case 'awd':
 			case 'awd':
 
 
-				var reader = new FileReader();
 				reader.addEventListener( 'load', function ( event ) {
 				reader.addEventListener( 'load', function ( event ) {
 
 
 					var loader = new THREE.AWDLoader();
 					var loader = new THREE.AWDLoader();
 					var scene = loader.parse( event.target.result );
 					var scene = loader.parse( event.target.result );
 
 
-					editor.setScene( scene );
+					editor.execute( new SetSceneCommand( scene ) );
 
 
 				}, false );
 				}, false );
 				reader.readAsArrayBuffer( file );
 				reader.readAsArrayBuffer( file );
@@ -49,7 +55,6 @@ var Loader = function ( editor ) {
 
 
 			case 'babylon':
 			case 'babylon':
 
 
-				var reader = new FileReader();
 				reader.addEventListener( 'load', function ( event ) {
 				reader.addEventListener( 'load', function ( event ) {
 
 
 					var contents = event.target.result;
 					var contents = event.target.result;
@@ -58,7 +63,7 @@ var Loader = function ( editor ) {
 					var loader = new THREE.BabylonLoader();
 					var loader = new THREE.BabylonLoader();
 					var scene = loader.parse( json );
 					var scene = loader.parse( json );
 
 
-					editor.setScene( scene );
+					editor.execute( new SetSceneCommand( scene ) );
 
 
 				}, false );
 				}, false );
 				reader.readAsText( file );
 				reader.readAsText( file );
@@ -67,7 +72,6 @@ var Loader = function ( editor ) {
 
 
 			case 'babylonmeshdata':
 			case 'babylonmeshdata':
 
 
-				var reader = new FileReader();
 				reader.addEventListener( 'load', function ( event ) {
 				reader.addEventListener( 'load', function ( event ) {
 
 
 					var contents = event.target.result;
 					var contents = event.target.result;
@@ -76,13 +80,12 @@ var Loader = function ( editor ) {
 					var loader = new THREE.BabylonLoader();
 					var loader = new THREE.BabylonLoader();
 
 
 					var geometry = loader.parseGeometry( json );
 					var geometry = loader.parseGeometry( json );
-					var material = new THREE.MeshPhongMaterial();
+					var material = new THREE.MeshStandardMaterial();
 
 
 					var mesh = new THREE.Mesh( geometry, material );
 					var mesh = new THREE.Mesh( geometry, material );
 					mesh.name = filename;
 					mesh.name = filename;
 
 
-					editor.addObject( mesh );
-					editor.select( mesh );
+					editor.execute( new AddObjectCommand( mesh ) );
 
 
 				}, false );
 				}, false );
 				reader.readAsText( file );
 				reader.readAsText( file );
@@ -91,7 +94,6 @@ var Loader = function ( editor ) {
 
 
 			case 'ctm':
 			case 'ctm':
 
 
-				var reader = new FileReader();
 				reader.addEventListener( 'load', function ( event ) {
 				reader.addEventListener( 'load', function ( event ) {
 
 
 					var data = new Uint8Array( event.target.result );
 					var data = new Uint8Array( event.target.result );
@@ -105,13 +107,12 @@ var Loader = function ( editor ) {
 						geometry.sourceType = "ctm";
 						geometry.sourceType = "ctm";
 						geometry.sourceFile = file.name;
 						geometry.sourceFile = file.name;
 
 
-						var material = new THREE.MeshPhongMaterial();
+						var material = new THREE.MeshStandardMaterial();
 
 
 						var mesh = new THREE.Mesh( geometry, material );
 						var mesh = new THREE.Mesh( geometry, material );
 						mesh.name = filename;
 						mesh.name = filename;
 
 
-						editor.addObject( mesh );
-						editor.select( mesh );
+						editor.execute( new AddObjectCommand( mesh ) );
 
 
 					} );
 					} );
 
 
@@ -122,7 +123,6 @@ var Loader = function ( editor ) {
 
 
 			case 'dae':
 			case 'dae':
 
 
-				var reader = new FileReader();
 				reader.addEventListener( 'load', function ( event ) {
 				reader.addEventListener( 'load', function ( event ) {
 
 
 					var contents = event.target.result;
 					var contents = event.target.result;
@@ -132,8 +132,23 @@ var Loader = function ( editor ) {
 
 
 					collada.scene.name = filename;
 					collada.scene.name = filename;
 
 
-					editor.addObject( collada.scene );
-					editor.select( collada.scene );
+					editor.execute( new AddObjectCommand( collada.scene ) );
+
+				}, false );
+				reader.readAsText( file );
+
+				break;
+
+			case 'fbx':
+
+				reader.addEventListener( 'load', function ( event ) {
+
+					var contents = event.target.result;
+
+					var loader = new THREE.FBXLoader();
+					var object = loader.parse( contents );
+
+					editor.execute( new AddObjectCommand( object ) );
 
 
 				}, false );
 				}, false );
 				reader.readAsText( file );
 				reader.readAsText( file );
@@ -148,14 +163,13 @@ var Loader = function ( editor ) {
 			case '3obj':
 			case '3obj':
 			case '3scn':
 			case '3scn':
 
 
-				var reader = new FileReader();
 				reader.addEventListener( 'load', function ( event ) {
 				reader.addEventListener( 'load', function ( event ) {
 
 
 					var contents = event.target.result;
 					var contents = event.target.result;
 
 
 					// 2.0
 					// 2.0
 
 
-					if ( contents.indexOf( 'postMessage' ) !== -1 ) {
+					if ( contents.indexOf( 'postMessage' ) !== - 1 ) {
 
 
 						var blob = new Blob( [ contents ], { type: 'text/javascript' } );
 						var blob = new Blob( [ contents ], { type: 'text/javascript' } );
 						var url = URL.createObjectURL( blob );
 						var url = URL.createObjectURL( blob );
@@ -198,52 +212,47 @@ var Loader = function ( editor ) {
 				break;
 				break;
 
 
 
 
-				case 'kmz':
+			case 'kmz':
 
 
-					var reader = new FileReader();
-					reader.addEventListener( 'load', function ( event ) {
+				reader.addEventListener( 'load', function ( event ) {
 
 
-						var loader = new THREE.KMZLoader();
-						var collada = loader.parse( event.target.result );
+					var loader = new THREE.KMZLoader();
+					var collada = loader.parse( event.target.result );
 
 
-						collada.scene.name = filename;
+					collada.scene.name = filename;
 
 
-						editor.addObject( collada.scene );
-						editor.select( collada.scene );
+					editor.execute( new AddObjectCommand( collada.scene ) );
 
 
-					}, false );
-					reader.readAsArrayBuffer( file );
+				}, false );
+				reader.readAsArrayBuffer( file );
 
 
-					break;
+				break;
 
 
-				case 'md2':
+			case 'md2':
 
 
-					var reader = new FileReader();
-					reader.addEventListener( 'load', function ( event ) {
+				reader.addEventListener( 'load', function ( event ) {
 
 
-						var contents = event.target.result;
+					var contents = event.target.result;
 
 
-						var geometry = new THREE.MD2Loader().parse( contents );
-						var material = new THREE.MeshPhongMaterial( {
-							morphTargets: true,
-							morphNormals: true
-						} );
+					var geometry = new THREE.MD2Loader().parse( contents );
+					var material = new THREE.MeshStandardMaterial( {
+						morphTargets: true,
+						morphNormals: true
+					} );
 
 
-						var mesh = new THREE.Mesh( geometry, material );
-						mesh.mixer = new THREE.AnimationMixer( mesh )
-						mesh.name = filename;
+					var mesh = new THREE.Mesh( geometry, material );
+					mesh.mixer = new THREE.AnimationMixer( mesh );
+					mesh.name = filename;
 
 
-						editor.addObject( mesh );
-						editor.select( mesh );
+					editor.execute( new AddObjectCommand( mesh ) );
 
 
-					}, false );
-					reader.readAsArrayBuffer( file );
+				}, false );
+				reader.readAsArrayBuffer( file );
 
 
-					break;
+				break;
 
 
 			case 'obj':
 			case 'obj':
 
 
-				var reader = new FileReader();
 				reader.addEventListener( 'load', function ( event ) {
 				reader.addEventListener( 'load', function ( event ) {
 
 
 					var contents = event.target.result;
 					var contents = event.target.result;
@@ -251,8 +260,24 @@ var Loader = function ( editor ) {
 					var object = new THREE.OBJLoader().parse( contents );
 					var object = new THREE.OBJLoader().parse( contents );
 					object.name = filename;
 					object.name = filename;
 
 
-					editor.addObject( object );
-					editor.select( object );
+					editor.execute( new AddObjectCommand( object ) );
+
+				}, false );
+				reader.readAsText( file );
+
+				break;
+
+			case 'playcanvas':
+
+				reader.addEventListener( 'load', function ( event ) {
+
+					var contents = event.target.result;
+					var json = JSON.parse( contents );
+
+					var loader = new THREE.PlayCanvasLoader();
+					var object = loader.parse( json );
+
+					editor.execute( new AddObjectCommand( object ) );
 
 
 				}, false );
 				}, false );
 				reader.readAsText( file );
 				reader.readAsText( file );
@@ -261,7 +286,6 @@ var Loader = function ( editor ) {
 
 
 			case 'ply':
 			case 'ply':
 
 
-				var reader = new FileReader();
 				reader.addEventListener( 'load', function ( event ) {
 				reader.addEventListener( 'load', function ( event ) {
 
 
 					var contents = event.target.result;
 					var contents = event.target.result;
@@ -270,13 +294,12 @@ var Loader = function ( editor ) {
 					geometry.sourceType = "ply";
 					geometry.sourceType = "ply";
 					geometry.sourceFile = file.name;
 					geometry.sourceFile = file.name;
 
 
-					var material = new THREE.MeshPhongMaterial();
+					var material = new THREE.MeshStandardMaterial();
 
 
 					var mesh = new THREE.Mesh( geometry, material );
 					var mesh = new THREE.Mesh( geometry, material );
 					mesh.name = filename;
 					mesh.name = filename;
 
 
-					editor.addObject( mesh );
-					editor.select( mesh );
+					editor.execute( new AddObjectCommand( mesh ) );
 
 
 				}, false );
 				}, false );
 				reader.readAsText( file );
 				reader.readAsText( file );
@@ -285,7 +308,6 @@ var Loader = function ( editor ) {
 
 
 			case 'stl':
 			case 'stl':
 
 
-				var reader = new FileReader();
 				reader.addEventListener( 'load', function ( event ) {
 				reader.addEventListener( 'load', function ( event ) {
 
 
 					var contents = event.target.result;
 					var contents = event.target.result;
@@ -294,13 +316,12 @@ var Loader = function ( editor ) {
 					geometry.sourceType = "stl";
 					geometry.sourceType = "stl";
 					geometry.sourceFile = file.name;
 					geometry.sourceFile = file.name;
 
 
-					var material = new THREE.MeshPhongMaterial();
+					var material = new THREE.MeshStandardMaterial();
 
 
 					var mesh = new THREE.Mesh( geometry, material );
 					var mesh = new THREE.Mesh( geometry, material );
 					mesh.name = filename;
 					mesh.name = filename;
 
 
-					editor.addObject( mesh );
-					editor.select( mesh );
+					editor.execute( new AddObjectCommand( mesh ) );
 
 
 				}, false );
 				}, false );
 
 
@@ -319,7 +340,6 @@ var Loader = function ( editor ) {
 			/*
 			/*
 			case 'utf8':
 			case 'utf8':
 
 
-				var reader = new FileReader();
 				reader.addEventListener( 'load', function ( event ) {
 				reader.addEventListener( 'load', function ( event ) {
 
 
 					var contents = event.target.result;
 					var contents = event.target.result;
@@ -329,8 +349,7 @@ var Loader = function ( editor ) {
 
 
 					var mesh = new THREE.Mesh( geometry, material );
 					var mesh = new THREE.Mesh( geometry, material );
 
 
-					editor.addObject( mesh );
-					editor.select( mesh );
+					editor.execute( new AddObjectCommand( mesh ) );
 
 
 				}, false );
 				}, false );
 				reader.readAsBinaryString( file );
 				reader.readAsBinaryString( file );
@@ -340,7 +359,6 @@ var Loader = function ( editor ) {
 
 
 			case 'vtk':
 			case 'vtk':
 
 
-				var reader = new FileReader();
 				reader.addEventListener( 'load', function ( event ) {
 				reader.addEventListener( 'load', function ( event ) {
 
 
 					var contents = event.target.result;
 					var contents = event.target.result;
@@ -349,13 +367,12 @@ var Loader = function ( editor ) {
 					geometry.sourceType = "vtk";
 					geometry.sourceType = "vtk";
 					geometry.sourceFile = file.name;
 					geometry.sourceFile = file.name;
 
 
-					var material = new THREE.MeshPhongMaterial();
+					var material = new THREE.MeshStandardMaterial();
 
 
 					var mesh = new THREE.Mesh( geometry, material );
 					var mesh = new THREE.Mesh( geometry, material );
 					mesh.name = filename;
 					mesh.name = filename;
 
 
-					editor.addObject( mesh );
-					editor.select( mesh );
+					editor.execute( new AddObjectCommand( mesh ) );
 
 
 				}, false );
 				}, false );
 				reader.readAsText( file );
 				reader.readAsText( file );
@@ -364,14 +381,13 @@ var Loader = function ( editor ) {
 
 
 			case 'wrl':
 			case 'wrl':
 
 
-				var reader = new FileReader();
 				reader.addEventListener( 'load', function ( event ) {
 				reader.addEventListener( 'load', function ( event ) {
 
 
 					var contents = event.target.result;
 					var contents = event.target.result;
 
 
 					var result = new THREE.VRMLLoader().parse( contents );
 					var result = new THREE.VRMLLoader().parse( contents );
 
 
-					editor.setScene( result );
+					editor.execute( new SetSceneCommand( result ) );
 
 
 				}, false );
 				}, false );
 				reader.readAsText( file );
 				reader.readAsText( file );
@@ -386,9 +402,9 @@ var Loader = function ( editor ) {
 
 
 		}
 		}
 
 
-	}
+	};
 
 
-	var handleJSON = function ( data, file, filename ) {
+	function handleJSON( data, file, filename ) {
 
 
 		if ( data.metadata === undefined ) { // 2.0
 		if ( data.metadata === undefined ) { // 2.0
 
 
@@ -402,101 +418,114 @@ var Loader = function ( editor ) {
 
 
 		}
 		}
 
 
-		if ( data.metadata.version === undefined ) {
+		if ( data.metadata.formatVersion !== undefined ) {
 
 
 			data.metadata.version = data.metadata.formatVersion;
 			data.metadata.version = data.metadata.formatVersion;
 
 
 		}
 		}
 
 
-		if ( data.metadata.type === 'BufferGeometry' ) {
+		switch ( data.metadata.type.toLowerCase() ) {
 
 
-			var loader = new THREE.BufferGeometryLoader();
-			var result = loader.parse( data );
+			case 'buffergeometry':
 
 
-			var mesh = new THREE.Mesh( result );
+				var loader = new THREE.BufferGeometryLoader();
+				var result = loader.parse( data );
+
+				var mesh = new THREE.Mesh( result );
+
+				editor.execute( new AddObjectCommand( mesh ) );
+
+				break;
 
 
-			editor.addObject( mesh );
-			editor.select( mesh );
+			case 'geometry':
 
 
-		} else if ( data.metadata.type.toLowerCase() === 'geometry' ) {
+				var loader = new THREE.JSONLoader();
+				loader.setTexturePath( scope.texturePath );
 
 
-			var loader = new THREE.JSONLoader();
-			loader.setTexturePath( scope.texturePath );
+				var result = loader.parse( data );
 
 
-			var result = loader.parse( data );
+				var geometry = result.geometry;
+				var material;
 
 
-			var geometry = result.geometry;
-			var material;
+				if ( result.materials !== undefined ) {
 
 
-			if ( result.materials !== undefined ) {
+					if ( result.materials.length > 1 ) {
 
 
-				if ( result.materials.length > 1 ) {
+						material = new THREE.MultiMaterial( result.materials );
 
 
-					material = new THREE.MeshFaceMaterial( result.materials );
+					} else {
+
+						material = result.materials[ 0 ];
+
+					}
 
 
 				} else {
 				} else {
 
 
-					material = result.materials[ 0 ];
+					material = new THREE.MeshStandardMaterial();
 
 
 				}
 				}
 
 
-			} else {
+				geometry.sourceType = "ascii";
+				geometry.sourceFile = file.name;
+
+				var mesh;
 
 
-				material = new THREE.MeshPhongMaterial();
+				if ( geometry.animation && geometry.animation.hierarchy ) {
 
 
-			}
+					mesh = new THREE.SkinnedMesh( geometry, material );
 
 
-			geometry.sourceType = "ascii";
-			geometry.sourceFile = file.name;
+				} else {
+
+					mesh = new THREE.Mesh( geometry, material );
+
+				}
 
 
-			var mesh;
+				mesh.name = filename;
 
 
-			if ( geometry.animation && geometry.animation.hierarchy ) {
+				editor.execute( new AddObjectCommand( mesh ) );
 
 
-				mesh = new THREE.SkinnedMesh( geometry, material );
+				break;
 
 
-			} else {
+			case 'object':
 
 
-				mesh = new THREE.Mesh( geometry, material );
+				var loader = new THREE.ObjectLoader();
+				loader.setTexturePath( scope.texturePath );
 
 
-			}
+				var result = loader.parse( data );
 
 
-			mesh.name = filename;
+				if ( result instanceof THREE.Scene ) {
 
 
-			editor.addObject( mesh );
-			editor.select( mesh );
+					editor.execute( new SetSceneCommand( result ) );
 
 
-		} else if ( data.metadata.type.toLowerCase() === 'object' ) {
+				} else {
 
 
-			var loader = new THREE.ObjectLoader();
-			loader.setTexturePath( scope.texturePath );
+					editor.execute( new AddObjectCommand( result ) );
 
 
-			var result = loader.parse( data );
+				}
 
 
-			if ( result instanceof THREE.Scene ) {
+				break;
 
 
-				editor.setScene( result );
+			case 'scene':
 
 
-			} else {
+				// DEPRECATED
 
 
-				editor.addObject( result );
-				editor.select( result );
+				var loader = new THREE.SceneLoader();
+				loader.parse( data, function ( result ) {
 
 
-			}
+					editor.execute( new SetSceneCommand( result.scene ) );
 
 
-		} else if ( data.metadata.type.toLowerCase() === 'scene' ) {
+				}, '' );
 
 
-			// DEPRECATED
+				break;
 
 
-			var loader = new THREE.SceneLoader();
-			loader.parse( data, function ( result ) {
+			case 'app':
 
 
-				editor.setScene( result.scene );
+				editor.fromJSON( data );
 
 
-			}, '' );
+				break;
 
 
 		}
 		}
 
 
-	};
+	}
 
 
-}
+};

+ 93 - 91
editor/js/Menubar.Add.js

@@ -32,7 +32,7 @@ Menubar.Add = function ( editor ) {
 
 
 	// Group
 	// Group
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'Group' );
 	option.setTextContent( 'Group' );
 	option.onClick( function () {
 	option.onClick( function () {
@@ -40,8 +40,7 @@ Menubar.Add = function ( editor ) {
 		var mesh = new THREE.Group();
 		var mesh = new THREE.Group();
 		mesh.name = 'Group ' + ( ++ meshCount );
 		mesh.name = 'Group ' + ( ++ meshCount );
 
 
-		editor.addObject( mesh );
-		editor.select( mesh );
+		editor.execute( new AddObjectCommand( mesh ) );
 
 
 	} );
 	} );
 	options.add( option );
 	options.add( option );
@@ -52,105 +51,87 @@ Menubar.Add = function ( editor ) {
 
 
 	// Plane
 	// Plane
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'Plane' );
 	option.setTextContent( 'Plane' );
 	option.onClick( function () {
 	option.onClick( function () {
 
 
-		var width = 200;
-		var height = 200;
-
-		var widthSegments = 1;
-		var heightSegments = 1;
-
-		var geometry = new THREE.PlaneGeometry( width, height, widthSegments, heightSegments );
-		var material = new THREE.MeshPhongMaterial();
+		var geometry = new THREE.PlaneGeometry( 2, 2 );
+		var material = new THREE.MeshStandardMaterial();
 		var mesh = new THREE.Mesh( geometry, material );
 		var mesh = new THREE.Mesh( geometry, material );
 		mesh.name = 'Plane ' + ( ++ meshCount );
 		mesh.name = 'Plane ' + ( ++ meshCount );
 
 
-		editor.addObject( mesh );
-		editor.select( mesh );
+		editor.execute( new AddObjectCommand( mesh ) );
 
 
 	} );
 	} );
 	options.add( option );
 	options.add( option );
 
 
 	// Box
 	// Box
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'Box' );
 	option.setTextContent( 'Box' );
 	option.onClick( function () {
 	option.onClick( function () {
 
 
-		var width = 100;
-		var height = 100;
-		var depth = 100;
-
-		var widthSegments = 1;
-		var heightSegments = 1;
-		var depthSegments = 1;
-
-		var geometry = new THREE.BoxGeometry( width, height, depth, widthSegments, heightSegments, depthSegments );
-		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
+		var geometry = new THREE.BoxGeometry( 1, 1, 1 );
+		var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial() );
 		mesh.name = 'Box ' + ( ++ meshCount );
 		mesh.name = 'Box ' + ( ++ meshCount );
 
 
-		editor.addObject( mesh );
-		editor.select( mesh );
+		editor.execute( new AddObjectCommand( mesh ) );
 
 
 	} );
 	} );
 	options.add( option );
 	options.add( option );
 
 
 	// Circle
 	// Circle
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'Circle' );
 	option.setTextContent( 'Circle' );
 	option.onClick( function () {
 	option.onClick( function () {
 
 
-		var radius = 20;
+		var radius = 1;
 		var segments = 32;
 		var segments = 32;
 
 
 		var geometry = new THREE.CircleGeometry( radius, segments );
 		var geometry = new THREE.CircleGeometry( radius, segments );
-		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
+		var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial() );
 		mesh.name = 'Circle ' + ( ++ meshCount );
 		mesh.name = 'Circle ' + ( ++ meshCount );
 
 
-		editor.addObject( mesh );
-		editor.select( mesh );
+		editor.execute( new AddObjectCommand( mesh ) );
 
 
 	} );
 	} );
 	options.add( option );
 	options.add( option );
 
 
 	// Cylinder
 	// Cylinder
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'Cylinder' );
 	option.setTextContent( 'Cylinder' );
 	option.onClick( function () {
 	option.onClick( function () {
 
 
-		var radiusTop = 20;
-		var radiusBottom = 20;
-		var height = 100;
+		var radiusTop = 1;
+		var radiusBottom = 1;
+		var height = 2;
 		var radiusSegments = 32;
 		var radiusSegments = 32;
 		var heightSegments = 1;
 		var heightSegments = 1;
 		var openEnded = false;
 		var openEnded = false;
 
 
 		var geometry = new THREE.CylinderGeometry( radiusTop, radiusBottom, height, radiusSegments, heightSegments, openEnded );
 		var geometry = new THREE.CylinderGeometry( radiusTop, radiusBottom, height, radiusSegments, heightSegments, openEnded );
-		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
+		var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial() );
 		mesh.name = 'Cylinder ' + ( ++ meshCount );
 		mesh.name = 'Cylinder ' + ( ++ meshCount );
 
 
-		editor.addObject( mesh );
-		editor.select( mesh );
+		editor.execute( new AddObjectCommand( mesh ) );
 
 
 	} );
 	} );
 	options.add( option );
 	options.add( option );
 
 
 	// Sphere
 	// Sphere
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'Sphere' );
 	option.setTextContent( 'Sphere' );
 	option.onClick( function () {
 	option.onClick( function () {
 
 
-		var radius = 75;
+		var radius = 1;
 		var widthSegments = 32;
 		var widthSegments = 32;
 		var heightSegments = 16;
 		var heightSegments = 16;
 		var phiStart = 0;
 		var phiStart = 0;
@@ -159,79 +140,75 @@ Menubar.Add = function ( editor ) {
 		var thetaLength = Math.PI;
 		var thetaLength = Math.PI;
 
 
 		var geometry = new THREE.SphereGeometry( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength );
 		var geometry = new THREE.SphereGeometry( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength );
-		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
+		var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial() );
 		mesh.name = 'Sphere ' + ( ++ meshCount );
 		mesh.name = 'Sphere ' + ( ++ meshCount );
 
 
-		editor.addObject( mesh );
-		editor.select( mesh );
+		editor.execute( new AddObjectCommand( mesh ) );
 
 
 	} );
 	} );
 	options.add( option );
 	options.add( option );
 
 
 	// Icosahedron
 	// Icosahedron
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'Icosahedron' );
 	option.setTextContent( 'Icosahedron' );
 	option.onClick( function () {
 	option.onClick( function () {
 
 
-		var radius = 75;
+		var radius = 1;
 		var detail = 2;
 		var detail = 2;
 
 
 		var geometry = new THREE.IcosahedronGeometry( radius, detail );
 		var geometry = new THREE.IcosahedronGeometry( radius, detail );
-		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
+		var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial() );
 		mesh.name = 'Icosahedron ' + ( ++ meshCount );
 		mesh.name = 'Icosahedron ' + ( ++ meshCount );
 
 
-		editor.addObject( mesh );
-		editor.select( mesh );
+		editor.execute( new AddObjectCommand( mesh ) );
 
 
 	} );
 	} );
 	options.add( option );
 	options.add( option );
 
 
 	// Torus
 	// Torus
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'Torus' );
 	option.setTextContent( 'Torus' );
 	option.onClick( function () {
 	option.onClick( function () {
 
 
-		var radius = 100;
-		var tube = 40;
-		var radialSegments = 8;
-		var tubularSegments = 6;
+		var radius = 2;
+		var tube = 1;
+		var radialSegments = 32;
+		var tubularSegments = 12;
 		var arc = Math.PI * 2;
 		var arc = Math.PI * 2;
 
 
 		var geometry = new THREE.TorusGeometry( radius, tube, radialSegments, tubularSegments, arc );
 		var geometry = new THREE.TorusGeometry( radius, tube, radialSegments, tubularSegments, arc );
-		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
+		var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial() );
 		mesh.name = 'Torus ' + ( ++ meshCount );
 		mesh.name = 'Torus ' + ( ++ meshCount );
 
 
-		editor.addObject( mesh );
-		editor.select( mesh );
+		editor.execute( new AddObjectCommand( mesh ) );
 
 
 	} );
 	} );
 	options.add( option );
 	options.add( option );
 
 
 	// TorusKnot
 	// TorusKnot
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'TorusKnot' );
 	option.setTextContent( 'TorusKnot' );
 	option.onClick( function () {
 	option.onClick( function () {
 
 
-		var radius = 100;
-		var tube = 40;
+		var radius = 2;
+		var tube = 0.8;
 		var radialSegments = 64;
 		var radialSegments = 64;
-		var tubularSegments = 8;
+		var tubularSegments = 12;
 		var p = 2;
 		var p = 2;
 		var q = 3;
 		var q = 3;
 		var heightScale = 1;
 		var heightScale = 1;
 
 
 		var geometry = new THREE.TorusKnotGeometry( radius, tube, radialSegments, tubularSegments, p, q, heightScale );
 		var geometry = new THREE.TorusKnotGeometry( radius, tube, radialSegments, tubularSegments, p, q, heightScale );
-		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
+		var mesh = new THREE.Mesh( geometry, new THREE.MeshStandardMaterial() );
 		mesh.name = 'TorusKnot ' + ( ++ meshCount );
 		mesh.name = 'TorusKnot ' + ( ++ meshCount );
 
 
-		editor.addObject( mesh );
-		editor.select( mesh );
+		editor.execute( new AddObjectCommand( mesh ) );
 
 
 	} );
 	} );
 	options.add( option );
 	options.add( option );
@@ -239,7 +216,7 @@ Menubar.Add = function ( editor ) {
 	/*
 	/*
 	// Teapot
 	// Teapot
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'Teapot' );
 	option.setTextContent( 'Teapot' );
 	option.onClick( function () {
 	option.onClick( function () {
@@ -252,8 +229,7 @@ Menubar.Add = function ( editor ) {
 		var fitLid = false;
 		var fitLid = false;
 		var blinnScale = true;
 		var blinnScale = true;
 
 
-		var material = new THREE.MeshPhongMaterial();
-		material.side = 2;
+		var material = new THREE.MeshStandardMaterial();
 
 
 		var geometry = new THREE.TeapotBufferGeometry( size, segments, bottom, lid, body, fitLid, blinnScale );
 		var geometry = new THREE.TeapotBufferGeometry( size, segments, bottom, lid, body, fitLid, blinnScale );
 		var mesh = new THREE.Mesh( geometry, material );
 		var mesh = new THREE.Mesh( geometry, material );
@@ -266,9 +242,42 @@ Menubar.Add = function ( editor ) {
 	options.add( option );
 	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( 4, 0 ),
+			new THREE.Vector2( 3.5, 0.5 ),
+			new THREE.Vector2( 1, 0.75 ),
+			new THREE.Vector2( 0.8, 1 ),
+			new THREE.Vector2( 0.8, 4 ),
+			new THREE.Vector2( 1, 4.2 ),
+			new THREE.Vector2( 1.4, 4.8 ),
+			new THREE.Vector2( 2, 5 ),
+			new THREE.Vector2( 2.5, 5.4 ),
+			new THREE.Vector2( 3, 12 )
+		];
+		var segments = 20;
+		var phiStart = 0;
+		var phiLength = 2 * Math.PI;
+
+		var geometry = new THREE.LatheGeometry( points, segments, phiStart, phiLength );
+		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
 	// Sprite
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'Sprite' );
 	option.setTextContent( 'Sprite' );
 	option.onClick( function () {
 	option.onClick( function () {
@@ -276,8 +285,7 @@ Menubar.Add = function ( editor ) {
 		var sprite = new THREE.Sprite( new THREE.SpriteMaterial() );
 		var sprite = new THREE.Sprite( new THREE.SpriteMaterial() );
 		sprite.name = 'Sprite ' + ( ++ meshCount );
 		sprite.name = 'Sprite ' + ( ++ meshCount );
 
 
-		editor.addObject( sprite );
-		editor.select( sprite );
+		editor.execute( new AddObjectCommand( sprite ) );
 
 
 	} );
 	} );
 	options.add( option );
 	options.add( option );
@@ -288,7 +296,7 @@ Menubar.Add = function ( editor ) {
 
 
 	// PointLight
 	// PointLight
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'PointLight' );
 	option.setTextContent( 'PointLight' );
 	option.onClick( function () {
 	option.onClick( function () {
@@ -300,15 +308,14 @@ Menubar.Add = function ( editor ) {
 		var light = new THREE.PointLight( color, intensity, distance );
 		var light = new THREE.PointLight( color, intensity, distance );
 		light.name = 'PointLight ' + ( ++ lightCount );
 		light.name = 'PointLight ' + ( ++ lightCount );
 
 
-		editor.addObject( light );
-		editor.select( light );
+		editor.execute( new AddObjectCommand( light ) );
 
 
 	} );
 	} );
 	options.add( option );
 	options.add( option );
 
 
 	// SpotLight
 	// SpotLight
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'SpotLight' );
 	option.setTextContent( 'SpotLight' );
 	option.onClick( function () {
 	option.onClick( function () {
@@ -323,17 +330,16 @@ Menubar.Add = function ( editor ) {
 		light.name = 'SpotLight ' + ( ++ lightCount );
 		light.name = 'SpotLight ' + ( ++ lightCount );
 		light.target.name = 'SpotLight ' + ( lightCount ) + ' Target';
 		light.target.name = 'SpotLight ' + ( lightCount ) + ' Target';
 
 
-		light.position.set( 0.5, 1, 0.75 ).multiplyScalar( 200 );
+		light.position.set( 5, 10, 7.5 );
 
 
-		editor.addObject( light );
-		editor.select( light );
+		editor.execute( new AddObjectCommand( light ) );
 
 
 	} );
 	} );
 	options.add( option );
 	options.add( option );
 
 
 	// DirectionalLight
 	// DirectionalLight
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'DirectionalLight' );
 	option.setTextContent( 'DirectionalLight' );
 	option.onClick( function () {
 	option.onClick( function () {
@@ -345,17 +351,16 @@ Menubar.Add = function ( editor ) {
 		light.name = 'DirectionalLight ' + ( ++ lightCount );
 		light.name = 'DirectionalLight ' + ( ++ lightCount );
 		light.target.name = 'DirectionalLight ' + ( lightCount ) + ' Target';
 		light.target.name = 'DirectionalLight ' + ( lightCount ) + ' Target';
 
 
-		light.position.set( 0.5, 1, 0.75 ).multiplyScalar( 200 );
+		light.position.set( 5, 10, 7.5 );
 
 
-		editor.addObject( light );
-		editor.select( light );
+		editor.execute( new AddObjectCommand( light ) );
 
 
 	} );
 	} );
 	options.add( option );
 	options.add( option );
 
 
 	// HemisphereLight
 	// HemisphereLight
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'HemisphereLight' );
 	option.setTextContent( 'HemisphereLight' );
 	option.onClick( function () {
 	option.onClick( function () {
@@ -367,17 +372,16 @@ Menubar.Add = function ( editor ) {
 		var light = new THREE.HemisphereLight( skyColor, groundColor, intensity );
 		var light = new THREE.HemisphereLight( skyColor, groundColor, intensity );
 		light.name = 'HemisphereLight ' + ( ++ lightCount );
 		light.name = 'HemisphereLight ' + ( ++ lightCount );
 
 
-		light.position.set( 0.5, 1, 0.75 ).multiplyScalar( 200 );
+		light.position.set( 0, 10, 0 );
 
 
-		editor.addObject( light );
-		editor.select( light );
+		editor.execute( new AddObjectCommand( light ) );
 
 
 	} );
 	} );
 	options.add( option );
 	options.add( option );
 
 
 	// AmbientLight
 	// AmbientLight
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'AmbientLight' );
 	option.setTextContent( 'AmbientLight' );
 	option.onClick( function() {
 	option.onClick( function() {
@@ -387,8 +391,7 @@ Menubar.Add = function ( editor ) {
 		var light = new THREE.AmbientLight( color );
 		var light = new THREE.AmbientLight( color );
 		light.name = 'AmbientLight ' + ( ++ lightCount );
 		light.name = 'AmbientLight ' + ( ++ lightCount );
 
 
-		editor.addObject( light );
-		editor.select( light );
+		editor.execute( new AddObjectCommand( light ) );
 
 
 	} );
 	} );
 	options.add( option );
 	options.add( option );
@@ -399,7 +402,7 @@ Menubar.Add = function ( editor ) {
 
 
 	// PerspectiveCamera
 	// PerspectiveCamera
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'PerspectiveCamera' );
 	option.setTextContent( 'PerspectiveCamera' );
 	option.onClick( function() {
 	option.onClick( function() {
@@ -407,8 +410,7 @@ Menubar.Add = function ( editor ) {
 		var camera = new THREE.PerspectiveCamera( 50, 1, 1, 10000 );
 		var camera = new THREE.PerspectiveCamera( 50, 1, 1, 10000 );
 		camera.name = 'PerspectiveCamera ' + ( ++ cameraCount );
 		camera.name = 'PerspectiveCamera ' + ( ++ cameraCount );
 
 
-		editor.addObject( camera );
-		editor.select( camera );
+		editor.execute( new AddObjectCommand( camera ) );
 
 
 	} );
 	} );
 	options.add( option );
 	options.add( option );

+ 63 - 18
editor/js/Menubar.Edit.js

@@ -18,35 +18,73 @@ Menubar.Edit = function ( editor ) {
 
 
 	// Undo
 	// Undo
 
 
-	var option = new UI.Panel();
-	option.setClass( 'option' );
-	option.setTextContent( 'Undo' );
-	option.onClick( function () {
+	var undo = new UI.Row();
+	undo.setClass( 'option' );
+	undo.setTextContent( 'Undo (Ctrl+Z)' );
+	undo.onClick( function () {
 
 
-		editor.history.undo();
+		editor.undo();
 
 
 	} );
 	} );
-	options.add( option );
+	options.add( undo );
 
 
 	// Redo
 	// Redo
 
 
-	var option = new UI.Panel();
+	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.setClass( 'option' );
-	option.setTextContent( 'Redo' );
+	option.setTextContent( 'Clear History' );
 	option.onClick( function () {
 	option.onClick( function () {
 
 
-		editor.history.redo();
+		if ( confirm( 'The Undo/Redo History will be cleared. Are you sure?' ) ) {
+
+			editor.history.clear();
+
+		}
 
 
 	} );
 	} );
 	options.add( option );
 	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() );
 	options.add( new UI.HorizontalRule() );
 
 
 	// Clone
 	// Clone
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'Clone' );
 	option.setTextContent( 'Clone' );
 	option.onClick( function () {
 	option.onClick( function () {
@@ -57,15 +95,14 @@ Menubar.Edit = function ( editor ) {
 
 
 		object = object.clone();
 		object = object.clone();
 
 
-		editor.addObject( object );
-		editor.select( object );
+		editor.execute( new AddObjectCommand( object ) );
 
 
 	} );
 	} );
 	options.add( option );
 	options.add( option );
 
 
 	// Delete
 	// Delete
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'Delete' );
 	option.setTextContent( 'Delete' );
 	option.onClick( function () {
 	option.onClick( function () {
@@ -75,15 +112,16 @@ Menubar.Edit = function ( editor ) {
 		if ( confirm( 'Delete ' + object.name + '?' ) === false ) return;
 		if ( confirm( 'Delete ' + object.name + '?' ) === false ) return;
 
 
 		var parent = object.parent;
 		var parent = object.parent;
-		editor.removeObject( object );
-		editor.select( parent );
+		if ( parent === undefined ) return; // avoid deleting the camera or scene
+
+		editor.execute( new RemoveObjectCommand( object ) );
 
 
 	} );
 	} );
 	options.add( option );
 	options.add( option );
 
 
 	// Minify shaders
 	// Minify shaders
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'Minify Shaders' );
 	option.setTextContent( 'Minify Shaders' );
 	option.onClick( function() {
 	option.onClick( function() {
@@ -108,6 +146,7 @@ Menubar.Edit = function ( editor ) {
 
 
 		}
 		}
 
 
+		var cmds = [];
 		root.traverse( function ( object ) {
 		root.traverse( function ( object ) {
 
 
 			var material = object.material;
 			var material = object.material;
@@ -119,8 +158,8 @@ Menubar.Edit = function ( editor ) {
 					var shader = glslprep.minifyGlsl( [
 					var shader = glslprep.minifyGlsl( [
 							material.vertexShader, material.fragmentShader ] );
 							material.vertexShader, material.fragmentShader ] );
 
 
-					material.vertexShader = shader[ 0 ];
-					material.fragmentShader = shader[ 1 ];
+					cmds.push( new SetMaterialValueCommand( object, 'vertexShader', shader[ 0 ] ) );
+					cmds.push( new SetMaterialValueCommand( object, 'fragmentShader', shader[ 1 ] ) );
 
 
 					++nMaterialsChanged;
 					++nMaterialsChanged;
 
 
@@ -148,6 +187,12 @@ Menubar.Edit = function ( editor ) {
 
 
 		} );
 		} );
 
 
+		if ( nMaterialsChanged > 0 ) {
+
+			editor.execute( new MultiCmdsCommand( cmds ), 'Minify Shaders' );
+
+		}
+
 		window.alert( nMaterialsChanged +
 		window.alert( nMaterialsChanged +
 				" material(s) were changed.\n" + errors.join( "\n" ) );
 				" material(s) were changed.\n" + errors.join( "\n" ) );
 
 

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

@@ -33,7 +33,7 @@ Menubar.Examples = function ( editor ) {
 
 
 			var item = items[ i ];
 			var item = items[ i ];
 
 
-			var option = new UI.Panel();
+			var option = new UI.Row();
 			option.setClass( 'option' );
 			option.setClass( 'option' );
 			option.setTextContent( item.title );
 			option.setTextContent( item.title );
 			option.onClick( function () {
 			option.onClick( function () {

+ 65 - 71
editor/js/Menubar.File.js

@@ -18,7 +18,7 @@ Menubar.File = function ( editor ) {
 
 
 	// New
 	// New
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'New' );
 	option.setTextContent( 'New' );
 	option.onClick( function () {
 	option.onClick( function () {
@@ -46,7 +46,7 @@ Menubar.File = function ( editor ) {
 
 
 	} );
 	} );
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'Import' );
 	option.setTextContent( 'Import' );
 	option.onClick( function () {
 	option.onClick( function () {
@@ -62,7 +62,7 @@ Menubar.File = function ( editor ) {
 
 
 	// Export Geometry
 	// Export Geometry
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'Export Geometry' );
 	option.setTextContent( 'Export Geometry' );
 	option.onClick( function () {
 	option.onClick( function () {
@@ -88,20 +88,24 @@ Menubar.File = function ( editor ) {
 		var output = geometry.toJSON();
 		var output = geometry.toJSON();
 
 
 		try {
 		try {
+
 			output = JSON.stringify( output, null, '\t' );
 			output = JSON.stringify( output, null, '\t' );
 			output = output.replace( /[\n\t]+([\d\.e\-\[\]]+)/g, '$1' );
 			output = output.replace( /[\n\t]+([\d\.e\-\[\]]+)/g, '$1' );
+
 		} catch ( e ) {
 		} catch ( e ) {
+
 			output = JSON.stringify( output );
 			output = JSON.stringify( output );
+
 		}
 		}
 
 
-		exportString( output, 'geometry.json' );
+		saveString( output, 'geometry.json' );
 
 
 	} );
 	} );
 	options.add( option );
 	options.add( option );
 
 
 	// Export Object
 	// Export Object
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'Export Object' );
 	option.setTextContent( 'Export Object' );
 	option.onClick( function () {
 	option.onClick( function () {
@@ -118,20 +122,24 @@ Menubar.File = function ( editor ) {
 		var output = object.toJSON();
 		var output = object.toJSON();
 
 
 		try {
 		try {
+
 			output = JSON.stringify( output, null, '\t' );
 			output = JSON.stringify( output, null, '\t' );
 			output = output.replace( /[\n\t]+([\d\.e\-\[\]]+)/g, '$1' );
 			output = output.replace( /[\n\t]+([\d\.e\-\[\]]+)/g, '$1' );
+
 		} catch ( e ) {
 		} catch ( e ) {
+
 			output = JSON.stringify( output );
 			output = JSON.stringify( output );
+
 		}
 		}
 
 
-		exportString( output, 'model.json' );
+		saveString( output, 'model.json' );
 
 
 	} );
 	} );
 	options.add( option );
 	options.add( option );
 
 
 	// Export Scene
 	// Export Scene
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'Export Scene' );
 	option.setTextContent( 'Export Scene' );
 	option.onClick( function () {
 	option.onClick( function () {
@@ -139,20 +147,24 @@ Menubar.File = function ( editor ) {
 		var output = editor.scene.toJSON();
 		var output = editor.scene.toJSON();
 
 
 		try {
 		try {
+
 			output = JSON.stringify( output, null, '\t' );
 			output = JSON.stringify( output, null, '\t' );
 			output = output.replace( /[\n\t]+([\d\.e\-\[\]]+)/g, '$1' );
 			output = output.replace( /[\n\t]+([\d\.e\-\[\]]+)/g, '$1' );
+
 		} catch ( e ) {
 		} catch ( e ) {
+
 			output = JSON.stringify( output );
 			output = JSON.stringify( output );
+
 		}
 		}
 
 
-		exportString( output, 'scene.json' );
+		saveString( output, 'scene.json' );
 
 
 	} );
 	} );
 	options.add( option );
 	options.add( option );
 
 
 	// Export OBJ
 	// Export OBJ
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'Export OBJ' );
 	option.setTextContent( 'Export OBJ' );
 	option.onClick( function () {
 	option.onClick( function () {
@@ -168,21 +180,21 @@ Menubar.File = function ( editor ) {
 
 
 		var exporter = new THREE.OBJExporter();
 		var exporter = new THREE.OBJExporter();
 
 
-		exportString( exporter.parse( object ), 'model.obj' );
+		saveString( exporter.parse( object ), 'model.obj' );
 
 
 	} );
 	} );
 	options.add( option );
 	options.add( option );
 
 
 	// Export STL
 	// Export STL
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'Export STL' );
 	option.setTextContent( 'Export STL' );
 	option.onClick( function () {
 	option.onClick( function () {
 
 
 		var exporter = new THREE.STLExporter();
 		var exporter = new THREE.STLExporter();
 
 
-		exportString( exporter.parse( editor.scene ), 'model.stl' );
+		saveString( exporter.parse( editor.scene ), 'model.stl' );
 
 
 	} );
 	} );
 	options.add( option );
 	options.add( option );
@@ -193,60 +205,19 @@ Menubar.File = function ( editor ) {
 
 
 	// Publish
 	// Publish
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'Publish' );
 	option.setTextContent( 'Publish' );
 	option.onClick( function () {
 	option.onClick( function () {
 
 
-		var camera = editor.camera;
-
 		var zip = new JSZip();
 		var zip = new JSZip();
 
 
-		zip.file( 'index.html', [
-
-			'<!DOCTYPE html>',
-			'<html lang="en">',
-			'	<head>',
-			'		<title>three.js</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 {',
-			'			margin: 0px;',
-			'			overflow: hidden;',
-			'		}',
-			'		</style>',
-			'	</head>',
-			'	<body ontouchstart="">',
-			'		<script src="js/three.min.js"></script>',
-			'		<script src="js/app.js"></script>',
-			'		<script>',
-			'',
-			'			var loader = new THREE.XHRLoader();',
-			'			loader.load( \'app.json\', function ( text ) {',
-			'',
-			'				var player = new APP.Player();',
-			'				player.load( JSON.parse( text ) );',
-			'				player.setSize( window.innerWidth, window.innerHeight );',
-			'				player.play();',
-			'',
-			'				document.body.appendChild( player.dom );',
-			'',
-			'				window.addEventListener( \'resize\', function () {',
-			'					player.setSize( window.innerWidth, window.innerHeight );',
-			'				} );',
-			'',
-			'			} );',
-			'',
-			'		</script>',
-			'	</body>',
-			'</html>'
-
-		].join( '\n' ) );
-
 		//
 		//
 
 
 		var output = editor.toJSON();
 		var output = editor.toJSON();
+		output.metadata.type = 'App';
+		delete output.history;
+
 		output = JSON.stringify( output, null, '\t' );
 		output = JSON.stringify( output, null, '\t' );
 		output = output.replace( /[\n\t]+([\d\.e\-\[\]]+)/g, '$1' );
 		output = output.replace( /[\n\t]+([\d\.e\-\[\]]+)/g, '$1' );
 
 
@@ -256,11 +227,16 @@ Menubar.File = function ( editor ) {
 
 
 		var manager = new THREE.LoadingManager( function () {
 		var manager = new THREE.LoadingManager( function () {
 
 
-			location.href = 'data:application/zip;base64,' + zip.generate();
+			save( zip.generate( { type: 'blob' } ), 'download.zip' );
 
 
 		} );
 		} );
 
 
 		var loader = new THREE.XHRLoader( manager );
 		var loader = new THREE.XHRLoader( manager );
+		loader.load( 'js/libs/app/index.html', function ( content ) {
+
+			zip.file( 'index.html', content );
+
+		} );
 		loader.load( 'js/libs/app.js', function ( content ) {
 		loader.load( 'js/libs/app.js', function ( content ) {
 
 
 			zip.file( 'js/app.js', content );
 			zip.file( 'js/app.js', content );
@@ -275,6 +251,26 @@ Menubar.File = function ( editor ) {
 	} );
 	} );
 	options.add( option );
 	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 );
+	*/
+
 
 
 	//
 	//
 
 
@@ -282,23 +278,21 @@ Menubar.File = function ( editor ) {
 	link.style.display = 'none';
 	link.style.display = 'none';
 	document.body.appendChild( link ); // Firefox workaround, see #6594
 	document.body.appendChild( link ); // Firefox workaround, see #6594
 
 
-	var exportString = function ( output, filename ) {
-
-		var blob = new Blob( [ output ], { type: 'text/plain' } );
-		var objectURL = URL.createObjectURL( blob );
+	function save( blob, filename ) {
 
 
-		link.href = objectURL;
+		link.href = URL.createObjectURL( blob );
 		link.download = filename || 'data.json';
 		link.download = filename || 'data.json';
-		link.target = '_blank';
+		link.click();
+
+		// URL.revokeObjectURL( url ); breaks Firefox...
+
+	}
+
+	function saveString( text, filename ) {
 
 
-		var event = document.createEvent("MouseEvents");
-		event.initMouseEvent(
-			"click", true, false, window, 0, 0, 0, 0, 0
-			, false, false, false, false, 0, null
-		);
-		link.dispatchEvent(event);
+		save( new Blob( [ text ], { type: 'text/plain' } ), filename );
 
 
-	};
+	}
 
 
 	return container;
 	return container;
 
 

+ 2 - 2
editor/js/Menubar.Help.js

@@ -18,7 +18,7 @@ Menubar.Help = function ( editor ) {
 
 
 	// Source code
 	// Source code
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'Source code' );
 	option.setTextContent( 'Source code' );
 	option.onClick( function () {
 	option.onClick( function () {
@@ -30,7 +30,7 @@ Menubar.Help = function ( editor ) {
 
 
 	// About
 	// About
 
 
-	var option = new UI.Panel();
+	var option = new UI.Row();
 	option.setClass( 'option' );
 	option.setClass( 'option' );
 	option.setTextContent( 'About' );
 	option.setTextContent( 'About' );
 	option.onClick( function () {
 	option.onClick( function () {

+ 6 - 9
editor/js/Menubar.Status.js

@@ -7,8 +7,9 @@ Menubar.Status = function ( editor ) {
 	var container = new UI.Panel();
 	var container = new UI.Panel();
 	container.setClass( 'menu right' );
 	container.setClass( 'menu right' );
 
 
-	var checkbox = new UI.Checkbox( editor.config.getKey( 'autosave' ) );
-	checkbox.onChange( function () {
+	var autosave = new UI.THREE.Boolean( editor.config.getKey( 'autosave' ), 'autosave' );
+	autosave.text.setColor( '#888' );
+	autosave.onChange( function () {
 
 
 		var value = this.getValue();
 		var value = this.getValue();
 
 
@@ -21,21 +22,17 @@ Menubar.Status = function ( editor ) {
 		}
 		}
 
 
 	} );
 	} );
-	container.add( checkbox );
-
-	var text = new UI.Text( 'autosave' );
-	text.setClass( 'title' );
-	container.add( text );
+	container.add( autosave );
 
 
 	editor.signals.savingStarted.add( function () {
 	editor.signals.savingStarted.add( function () {
 
 
-		text.setTextDecoration( 'underline' );
+		autosave.text.setTextDecoration( 'underline' );
 
 
 	} );
 	} );
 
 
 	editor.signals.savingFinished.add( function () {
 	editor.signals.savingFinished.add( function () {
 
 
-		text.setTextDecoration( 'none' );
+		autosave.text.setTextDecoration( 'none' );
 
 
 	} );
 	} );
 
 

+ 0 - 81
editor/js/Menubar.View.js

@@ -1,81 +0,0 @@
-/**
- * @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 );
-
-	// Light theme
-
-	var option = new UI.Panel();
-	option.setClass( 'option' );
-	option.setTextContent( 'Light theme' );
-	option.onClick( function () {
-
-		editor.setTheme( 'css/light.css' );
-		editor.config.setKey( 'theme', 'css/light.css' );
-
-	} );
-	options.add( option );
-
-	// Dark theme
-
-	var option = new UI.Panel();
-	option.setClass( 'option' );
-	option.setTextContent( 'Dark theme' );
-	option.onClick( function () {
-
-		editor.setTheme( 'css/dark.css' );
-		editor.config.setKey( 'theme', 'css/dark.css' );
-
-	} );
-	options.add( option );
-
-	//
-
-	options.add( new UI.HorizontalRule() );
-
-	// fullscreen
-
-	var option = new UI.Panel();
-	option.setClass( 'option' );
-	option.setTextContent( 'Fullscreen' );
-	option.onClick( function () {
-
-		var element = document.body;
-
-		if ( element.requestFullscreen ) {
-
-			element.requestFullscreen();
-
-		} else if ( element.mozRequestFullScreen ) {
-
-			element.mozRequestFullScreen();
-
-		} else if ( element.webkitRequestFullscreen ) {
-
-			element.webkitRequestFullscreen();
-
-		} else if ( element.msRequestFullscreen ) {
-
-			element.msRequestFullscreen();
-
-		}
-
-	} );
-	options.add( option );
-
-	return container;
-
-};

+ 0 - 1
editor/js/Menubar.js

@@ -12,7 +12,6 @@ var Menubar = function ( editor ) {
 	container.add( new Menubar.Add( editor ) );
 	container.add( new Menubar.Add( editor ) );
 	container.add( new Menubar.Play( editor ) );
 	container.add( new Menubar.Play( editor ) );
 	container.add( new Menubar.Examples( editor ) );
 	container.add( new Menubar.Examples( editor ) );
-	container.add( new Menubar.View( editor ) );
 	container.add( new Menubar.Help( editor ) );
 	container.add( new Menubar.Help( editor ) );
 
 
 	container.add( new Menubar.Status( editor ) );
 	container.add( new Menubar.Status( editor ) );

+ 66 - 17
editor/js/Script.js

@@ -80,20 +80,39 @@ var Script = function ( editor ) {
 
 
 			if ( typeof( currentScript ) === 'object' ) {
 			if ( typeof( currentScript ) === 'object' ) {
 
 
-				currentScript.source = value;
-				signals.scriptChanged.dispatch( currentScript );
+				if ( value !== currentScript.source ) {
+
+					editor.execute( new SetScriptValueCommand( currentObject, currentScript, 'source', value, codemirror.getCursor() ) );
+
+				}
 				return;
 				return;
 			}
 			}
 
 
 			if ( currentScript !== 'programInfo' ) return;
 			if ( currentScript !== 'programInfo' ) return;
 
 
 			var json = JSON.parse( value );
 			var json = JSON.parse( value );
-			currentObject.defines = json.defines;
-			currentObject.uniforms = json.uniforms;
-			currentObject.attributes = json.attributes;
 
 
-			currentObject.needsUpdate = true;
-			signals.materialChanged.dispatch( currentObject );
+			if ( JSON.stringify( currentObject.material.defines ) !== JSON.stringify( json.defines ) ) {
+
+				var cmd = new SetMaterialValueCommand( currentObject, 'defines', json.defines );
+				cmd.updatable = false;
+				editor.execute( cmd );
+
+			}
+			if ( JSON.stringify( currentObject.material.uniforms ) !== JSON.stringify( json.uniforms ) ) {
+
+				var cmd = new SetMaterialValueCommand( currentObject, 'uniforms', json.uniforms );
+				cmd.updatable = false;
+				editor.execute( cmd );
+
+			}
+			if ( JSON.stringify( currentObject.material.attributes ) !== JSON.stringify( json.attributes ) ) {
+
+				var cmd = new SetMaterialValueCommand( currentObject, 'attributes', json.attributes );
+				cmd.updatable = false;
+				editor.execute( cmd );
+
+			}
 
 
 		}, 300 );
 		}, 300 );
 
 
@@ -222,9 +241,9 @@ var Script = function ( editor ) {
 					if ( errors.length !== 0 ) break;
 					if ( errors.length !== 0 ) break;
 					if ( renderer instanceof THREE.WebGLRenderer === false ) break;
 					if ( renderer instanceof THREE.WebGLRenderer === false ) break;
 
 
-					currentObject[ currentScript ] = string;
-					currentObject.needsUpdate = true;
-					signals.materialChanged.dispatch( currentObject );
+					currentObject.material[ currentScript ] = string;
+					currentObject.material.needsUpdate = true;
+					signals.materialChanged.dispatch( currentObject.material );
 
 
 					var programs = renderer.info.programs;
 					var programs = renderer.info.programs;
 
 
@@ -236,7 +255,7 @@ var Script = function ( editor ) {
 						var diagnostics = programs[i].diagnostics;
 						var diagnostics = programs[i].diagnostics;
 
 
 						if ( diagnostics === undefined ||
 						if ( diagnostics === undefined ||
-								diagnostics.material !== currentObject ) continue;
+								diagnostics.material !== currentObject.material ) continue;
 
 
 						if ( ! diagnostics.runnable ) valid = false;
 						if ( ! diagnostics.runnable ) valid = false;
 
 
@@ -342,6 +361,7 @@ var Script = function ( editor ) {
 			mode = 'javascript';
 			mode = 'javascript';
 			name = script.name;
 			name = script.name;
 			source = script.source;
 			source = script.source;
+			title.setValue( object.name + ' / ' + name );
 
 
 		} else {
 		} else {
 
 
@@ -351,7 +371,7 @@ var Script = function ( editor ) {
 
 
 					mode = 'glsl';
 					mode = 'glsl';
 					name = 'Vertex Shader';
 					name = 'Vertex Shader';
-					source = object.vertexShader || "";
+					source = object.material.vertexShader || "";
 
 
 					break;
 					break;
 
 
@@ -359,7 +379,7 @@ var Script = function ( editor ) {
 
 
 					mode = 'glsl';
 					mode = 'glsl';
 					name = 'Fragment Shader';
 					name = 'Fragment Shader';
-					source = object.fragmentShader || "";
+					source = object.material.fragmentShader || "";
 
 
 					break;
 					break;
 
 
@@ -368,13 +388,14 @@ var Script = function ( editor ) {
 					mode = 'json';
 					mode = 'json';
 					name = 'Program Properties';
 					name = 'Program Properties';
 					var json = {
 					var json = {
-						defines: object.defines,
-						uniforms: object.uniforms,
-						attributes: object.attributes
+						defines: object.material.defines,
+						uniforms: object.material.uniforms,
+						attributes: object.material.attributes
 					};
 					};
 					source = JSON.stringify( json, null, '\t' );
 					source = JSON.stringify( json, null, '\t' );
 
 
 			}
 			}
+			title.setValue( object.material.name + ' / ' + name );
 
 
 		}
 		}
 
 
@@ -382,7 +403,6 @@ var Script = function ( editor ) {
 		currentScript = script;
 		currentScript = script;
 		currentObject = object;
 		currentObject = object;
 
 
-		title.setValue( object.name + ' / ' + name );
 		container.setDisplay( '' );
 		container.setDisplay( '' );
 		codemirror.setValue( source );
 		codemirror.setValue( source );
 		if (mode === 'json' ) mode = { name: 'javascript', json: true };
 		if (mode === 'json' ) mode = { name: 'javascript', json: true };
@@ -390,6 +410,35 @@ var Script = function ( editor ) {
 
 
 	} );
 	} );
 
 
+	signals.scriptRemoved.add( function ( script ) {
+
+		if ( currentScript === script ) {
+
+			container.setDisplay( 'none' );
+
+		}
+
+	} );
+
+	signals.refreshScriptEditor.add( function ( object, script, cursorPosition ) {
+
+		if ( currentScript !== script ) return;
+
+		// copying the codemirror history because "codemirror.setValue(...)" alters its history
+
+		var history = codemirror.getHistory();
+		title.setValue( object.name + ' / ' + script.name );
+		codemirror.setValue( script.source );
+
+		if ( cursorPosition !== undefined ) {
+
+			codemirror.setCursor( cursorPosition );
+
+		}
+		codemirror.setHistory( history ); // setting the history to previous state
+
+	} );
+
 	return container;
 	return container;
 
 
 };
 };

+ 2 - 2
editor/js/Sidebar.Animation.js

@@ -21,7 +21,7 @@ Sidebar.Animation = function ( editor ) {
 	container.addStatic( new UI.Text( 'Animation' ).setTextTransform( 'uppercase' ) );
 	container.addStatic( new UI.Text( 'Animation' ).setTextTransform( 'uppercase' ) );
 	container.add( new UI.Break() );
 	container.add( new UI.Break() );
 
 
-	var animationsRow = new UI.Panel();
+	var animationsRow = new UI.Row();
 	container.add( animationsRow );
 	container.add( animationsRow );
 
 
 	/*
 	/*
@@ -36,7 +36,7 @@ Sidebar.Animation = function ( editor ) {
 
 
 				var material = child.material;
 				var material = child.material;
 
 
-				if ( material instanceof THREE.MeshFaceMaterial ) {
+				if ( material instanceof THREE.MultiMaterial ) {
 
 
 					for ( var i = 0; i < material.materials.length; i ++ ) {
 					for ( var i = 0; i < material.materials.length; i ++ ) {
 
 

+ 12 - 16
editor/js/Sidebar.Geometry.BoxGeometry.js

@@ -2,15 +2,17 @@
  * @author mrdoob / http://mrdoob.com/
  * @author mrdoob / http://mrdoob.com/
  */
  */
 
 
-Sidebar.Geometry.BoxGeometry = function ( signals, object ) {
+Sidebar.Geometry.BoxGeometry = function ( editor, object ) {
 
 
-	var container = new UI.Panel();
+	var signals = editor.signals;
+
+	var container = new UI.Row();
 
 
 	var parameters = object.geometry.parameters;
 	var parameters = object.geometry.parameters;
 
 
 	// width
 	// width
 
 
-	var widthRow = new UI.Panel();
+	var widthRow = new UI.Row();
 	var width = new UI.Number( parameters.width ).onChange( update );
 	var width = new UI.Number( parameters.width ).onChange( update );
 
 
 	widthRow.add( new UI.Text( 'Width' ).setWidth( '90px' ) );
 	widthRow.add( new UI.Text( 'Width' ).setWidth( '90px' ) );
@@ -20,7 +22,7 @@ Sidebar.Geometry.BoxGeometry = function ( signals, object ) {
 
 
 	// height
 	// height
 
 
-	var heightRow = new UI.Panel();
+	var heightRow = new UI.Row();
 	var height = new UI.Number( parameters.height ).onChange( update );
 	var height = new UI.Number( parameters.height ).onChange( update );
 
 
 	heightRow.add( new UI.Text( 'Height' ).setWidth( '90px' ) );
 	heightRow.add( new UI.Text( 'Height' ).setWidth( '90px' ) );
@@ -30,7 +32,7 @@ Sidebar.Geometry.BoxGeometry = function ( signals, object ) {
 
 
 	// depth
 	// depth
 
 
-	var depthRow = new UI.Panel();
+	var depthRow = new UI.Row();
 	var depth = new UI.Number( parameters.depth ).onChange( update );
 	var depth = new UI.Number( parameters.depth ).onChange( update );
 
 
 	depthRow.add( new UI.Text( 'Depth' ).setWidth( '90px' ) );
 	depthRow.add( new UI.Text( 'Depth' ).setWidth( '90px' ) );
@@ -40,7 +42,7 @@ Sidebar.Geometry.BoxGeometry = function ( signals, object ) {
 
 
 	// widthSegments
 	// widthSegments
 
 
-	var widthSegmentsRow = new UI.Panel();
+	var widthSegmentsRow = new UI.Row();
 	var widthSegments = new UI.Integer( parameters.widthSegments ).setRange( 1, Infinity ).onChange( update );
 	var widthSegments = new UI.Integer( parameters.widthSegments ).setRange( 1, Infinity ).onChange( update );
 
 
 	widthSegmentsRow.add( new UI.Text( 'Width segments' ).setWidth( '90px' ) );
 	widthSegmentsRow.add( new UI.Text( 'Width segments' ).setWidth( '90px' ) );
@@ -50,7 +52,7 @@ Sidebar.Geometry.BoxGeometry = function ( signals, object ) {
 
 
 	// heightSegments
 	// heightSegments
 
 
-	var heightSegmentsRow = new UI.Panel();
+	var heightSegmentsRow = new UI.Row();
 	var heightSegments = new UI.Integer( parameters.heightSegments ).setRange( 1, Infinity ).onChange( update );
 	var heightSegments = new UI.Integer( parameters.heightSegments ).setRange( 1, Infinity ).onChange( update );
 
 
 	heightSegmentsRow.add( new UI.Text( 'Height segments' ).setWidth( '90px' ) );
 	heightSegmentsRow.add( new UI.Text( 'Height segments' ).setWidth( '90px' ) );
@@ -60,7 +62,7 @@ Sidebar.Geometry.BoxGeometry = function ( signals, object ) {
 
 
 	// depthSegments
 	// depthSegments
 
 
-	var depthSegmentsRow = new UI.Panel();
+	var depthSegmentsRow = new UI.Row();
 	var depthSegments = new UI.Integer( parameters.depthSegments ).setRange( 1, Infinity ).onChange( update );
 	var depthSegments = new UI.Integer( parameters.depthSegments ).setRange( 1, Infinity ).onChange( update );
 
 
 	depthSegmentsRow.add( new UI.Text( 'Height segments' ).setWidth( '90px' ) );
 	depthSegmentsRow.add( new UI.Text( 'Height segments' ).setWidth( '90px' ) );
@@ -72,20 +74,14 @@ Sidebar.Geometry.BoxGeometry = function ( signals, object ) {
 
 
 	function update() {
 	function update() {
 
 
-		object.geometry.dispose();
-
-		object.geometry = new THREE.BoxGeometry(
+		editor.execute( new SetGeometryCommand( object, new THREE.BoxGeometry(
 			width.getValue(),
 			width.getValue(),
 			height.getValue(),
 			height.getValue(),
 			depth.getValue(),
 			depth.getValue(),
 			widthSegments.getValue(),
 			widthSegments.getValue(),
 			heightSegments.getValue(),
 			heightSegments.getValue(),
 			depthSegments.getValue()
 			depthSegments.getValue()
-		);
-
-		object.geometry.computeBoundingSphere();
-
-		signals.geometryChanged.dispatch( object );
+		) ) );
 
 
 	}
 	}
 
 

+ 11 - 7
editor/js/Sidebar.Geometry.BufferGeometry.js

@@ -2,9 +2,11 @@
  * @author mrdoob / http://mrdoob.com/
  * @author mrdoob / http://mrdoob.com/
  */
  */
 
 
-Sidebar.Geometry.BufferGeometry = function ( signals ) {
+Sidebar.Geometry.BufferGeometry = function ( editor ) {
 
 
-	var container = new UI.Panel();
+	var signals = editor.signals;
+
+	var container = new UI.Row();
 
 
 	function update( object ) {
 	function update( object ) {
 
 
@@ -21,7 +23,7 @@ Sidebar.Geometry.BufferGeometry = function ( signals ) {
 
 
 			if ( index !== null ) {
 			if ( index !== null ) {
 
 
-				var panel = new UI.Panel();
+				var panel = new UI.Row();
 				panel.add( new UI.Text( 'index' ).setWidth( '90px' ) );
 				panel.add( new UI.Text( 'index' ).setWidth( '90px' ) );
 				panel.add( new UI.Text( ( index.count ).format() ).setFontSize( '12px' ) );
 				panel.add( new UI.Text( ( index.count ).format() ).setFontSize( '12px' ) );
 				container.add( panel );
 				container.add( panel );
@@ -32,9 +34,11 @@ Sidebar.Geometry.BufferGeometry = function ( signals ) {
 
 
 			for ( var name in attributes ) {
 			for ( var name in attributes ) {
 
 
-				var panel = new UI.Panel();
+				var attribute = attributes[ name ];
+
+				var panel = new UI.Row();
 				panel.add( new UI.Text( name ).setWidth( '90px' ) );
 				panel.add( new UI.Text( name ).setWidth( '90px' ) );
-				panel.add( new UI.Text( ( attributes[ name ].count ).format() ).setFontSize( '12px' ) );
+				panel.add( new UI.Text( ( attribute.count ).format() + ' (' + attribute.itemSize + ')' ).setFontSize( '12px' ) );
 				container.add( panel );
 				container.add( panel );
 
 
 			}
 			}
@@ -45,11 +49,11 @@ Sidebar.Geometry.BufferGeometry = function ( signals ) {
 
 
 		}
 		}
 
 
-	};
+	}
 
 
 	signals.objectSelected.add( update );
 	signals.objectSelected.add( update );
 	signals.geometryChanged.add( update );
 	signals.geometryChanged.add( update );
 
 
 	return container;
 	return container;
 
 
-}
+};

+ 10 - 12
editor/js/Sidebar.Geometry.CircleGeometry.js

@@ -2,15 +2,17 @@
  * @author mrdoob / http://mrdoob.com/
  * @author mrdoob / http://mrdoob.com/
  */
  */
 
 
-Sidebar.Geometry.CircleGeometry = function ( signals, object ) {
+Sidebar.Geometry.CircleGeometry = function ( editor, object ) {
 
 
-	var container = new UI.Panel();
+	var signals = editor.signals;
+
+	var container = new UI.Row();
 
 
 	var parameters = object.geometry.parameters;
 	var parameters = object.geometry.parameters;
 
 
 	// radius
 	// radius
 
 
-	var radiusRow = new UI.Panel();
+	var radiusRow = new UI.Row();
 	var radius = new UI.Number( parameters.radius ).onChange( update );
 	var radius = new UI.Number( parameters.radius ).onChange( update );
 
 
 	radiusRow.add( new UI.Text( 'Radius' ).setWidth( '90px' ) );
 	radiusRow.add( new UI.Text( 'Radius' ).setWidth( '90px' ) );
@@ -20,7 +22,7 @@ Sidebar.Geometry.CircleGeometry = function ( signals, object ) {
 
 
 	// segments
 	// segments
 
 
-	var segmentsRow = new UI.Panel();
+	var segmentsRow = new UI.Row();
 	var segments = new UI.Integer( parameters.segments ).setRange( 3, Infinity ).onChange( update );
 	var segments = new UI.Integer( parameters.segments ).setRange( 3, Infinity ).onChange( update );
 
 
 	segmentsRow.add( new UI.Text( 'Segments' ).setWidth( '90px' ) );
 	segmentsRow.add( new UI.Text( 'Segments' ).setWidth( '90px' ) );
@@ -30,7 +32,7 @@ Sidebar.Geometry.CircleGeometry = function ( signals, object ) {
 
 
 	// thetaStart
 	// thetaStart
 
 
-	var thetaStartRow = new UI.Panel();
+	var thetaStartRow = new UI.Row();
 	var thetaStart = new UI.Number( parameters.thetaStart ).onChange( update );
 	var thetaStart = new UI.Number( parameters.thetaStart ).onChange( update );
 
 
 	thetaStartRow.add( new UI.Text( 'Theta start' ).setWidth( '90px' ) );
 	thetaStartRow.add( new UI.Text( 'Theta start' ).setWidth( '90px' ) );
@@ -40,7 +42,7 @@ Sidebar.Geometry.CircleGeometry = function ( signals, object ) {
 
 
 	// thetaLength
 	// thetaLength
 
 
-	var thetaLengthRow = new UI.Panel();
+	var thetaLengthRow = new UI.Row();
 	var thetaLength = new UI.Number( parameters.thetaLength ).onChange( update );
 	var thetaLength = new UI.Number( parameters.thetaLength ).onChange( update );
 
 
 	thetaLengthRow.add( new UI.Text( 'Theta length' ).setWidth( '90px' ) );
 	thetaLengthRow.add( new UI.Text( 'Theta length' ).setWidth( '90px' ) );
@@ -52,16 +54,12 @@ Sidebar.Geometry.CircleGeometry = function ( signals, object ) {
 
 
 	function update() {
 	function update() {
 
 
-		object.geometry.dispose();
-
-		object.geometry = new THREE.CircleGeometry(
+		editor.execute( new SetGeometryCommand( object, new THREE.CircleGeometry(
 			radius.getValue(),
 			radius.getValue(),
 			segments.getValue(),
 			segments.getValue(),
 			thetaStart.getValue(),
 			thetaStart.getValue(),
 			thetaLength.getValue()
 			thetaLength.getValue()
-		);
-
-		signals.geometryChanged.dispatch( object );
+		) ) );
 
 
 	}
 	}
 
 

+ 12 - 16
editor/js/Sidebar.Geometry.CylinderGeometry.js

@@ -2,15 +2,17 @@
  * @author mrdoob / http://mrdoob.com/
  * @author mrdoob / http://mrdoob.com/
  */
  */
 
 
-Sidebar.Geometry.CylinderGeometry = function ( signals, object ) {
+Sidebar.Geometry.CylinderGeometry = function ( editor, object ) {
 
 
-	var container = new UI.Panel();
+	var signals = editor.signals;
+
+	var container = new UI.Row();
 
 
 	var parameters = object.geometry.parameters;
 	var parameters = object.geometry.parameters;
 
 
 	// radiusTop
 	// radiusTop
 
 
-	var radiusTopRow = new UI.Panel();
+	var radiusTopRow = new UI.Row();
 	var radiusTop = new UI.Number( parameters.radiusTop ).onChange( update );
 	var radiusTop = new UI.Number( parameters.radiusTop ).onChange( update );
 
 
 	radiusTopRow.add( new UI.Text( 'Radius top' ).setWidth( '90px' ) );
 	radiusTopRow.add( new UI.Text( 'Radius top' ).setWidth( '90px' ) );
@@ -20,7 +22,7 @@ Sidebar.Geometry.CylinderGeometry = function ( signals, object ) {
 
 
 	// radiusBottom
 	// radiusBottom
 
 
-	var radiusBottomRow = new UI.Panel();
+	var radiusBottomRow = new UI.Row();
 	var radiusBottom = new UI.Number( parameters.radiusBottom ).onChange( update );
 	var radiusBottom = new UI.Number( parameters.radiusBottom ).onChange( update );
 
 
 	radiusBottomRow.add( new UI.Text( 'Radius bottom' ).setWidth( '90px' ) );
 	radiusBottomRow.add( new UI.Text( 'Radius bottom' ).setWidth( '90px' ) );
@@ -30,7 +32,7 @@ Sidebar.Geometry.CylinderGeometry = function ( signals, object ) {
 
 
 	// height
 	// height
 
 
-	var heightRow = new UI.Panel();
+	var heightRow = new UI.Row();
 	var height = new UI.Number( parameters.height ).onChange( update );
 	var height = new UI.Number( parameters.height ).onChange( update );
 
 
 	heightRow.add( new UI.Text( 'Height' ).setWidth( '90px' ) );
 	heightRow.add( new UI.Text( 'Height' ).setWidth( '90px' ) );
@@ -40,7 +42,7 @@ Sidebar.Geometry.CylinderGeometry = function ( signals, object ) {
 
 
 	// radialSegments
 	// radialSegments
 
 
-	var radialSegmentsRow = new UI.Panel();
+	var radialSegmentsRow = new UI.Row();
 	var radialSegments = new UI.Integer( parameters.radialSegments ).setRange( 1, Infinity ).onChange( update );
 	var radialSegments = new UI.Integer( parameters.radialSegments ).setRange( 1, Infinity ).onChange( update );
 
 
 	radialSegmentsRow.add( new UI.Text( 'Radial segments' ).setWidth( '90px' ) );
 	radialSegmentsRow.add( new UI.Text( 'Radial segments' ).setWidth( '90px' ) );
@@ -50,7 +52,7 @@ Sidebar.Geometry.CylinderGeometry = function ( signals, object ) {
 
 
 	// heightSegments
 	// heightSegments
 
 
-	var heightSegmentsRow = new UI.Panel();
+	var heightSegmentsRow = new UI.Row();
 	var heightSegments = new UI.Integer( parameters.heightSegments ).setRange( 1, Infinity ).onChange( update );
 	var heightSegments = new UI.Integer( parameters.heightSegments ).setRange( 1, Infinity ).onChange( update );
 
 
 	heightSegmentsRow.add( new UI.Text( 'Height segments' ).setWidth( '90px' ) );
 	heightSegmentsRow.add( new UI.Text( 'Height segments' ).setWidth( '90px' ) );
@@ -60,7 +62,7 @@ Sidebar.Geometry.CylinderGeometry = function ( signals, object ) {
 
 
 	// openEnded
 	// openEnded
 
 
-	var openEndedRow = new UI.Panel();
+	var openEndedRow = new UI.Row();
 	var openEnded = new UI.Checkbox( parameters.openEnded ).onChange( update );
 	var openEnded = new UI.Checkbox( parameters.openEnded ).onChange( update );
 
 
 	openEndedRow.add( new UI.Text( 'Open ended' ).setWidth( '90px' ) );
 	openEndedRow.add( new UI.Text( 'Open ended' ).setWidth( '90px' ) );
@@ -72,20 +74,14 @@ Sidebar.Geometry.CylinderGeometry = function ( signals, object ) {
 
 
 	function update() {
 	function update() {
 
 
-		object.geometry.dispose();
-
-		object.geometry = new THREE.CylinderGeometry(
+		editor.execute( new SetGeometryCommand( object, new THREE.CylinderGeometry(
 			radiusTop.getValue(),
 			radiusTop.getValue(),
 			radiusBottom.getValue(),
 			radiusBottom.getValue(),
 			height.getValue(),
 			height.getValue(),
 			radialSegments.getValue(),
 			radialSegments.getValue(),
 			heightSegments.getValue(),
 			heightSegments.getValue(),
 			openEnded.getValue()
 			openEnded.getValue()
-		);
-
-		object.geometry.computeBoundingSphere();
-
-		signals.geometryChanged.dispatch( object );
+		) ) );
 
 
 	}
 	}
 
 

+ 9 - 7
editor/js/Sidebar.Geometry.Geometry.js

@@ -2,14 +2,16 @@
  * @author mrdoob / http://mrdoob.com/
  * @author mrdoob / http://mrdoob.com/
  */
  */
 
 
-Sidebar.Geometry.Geometry = function ( signals ) {
+Sidebar.Geometry.Geometry = function ( editor ) {
 
 
-	var container = new UI.Panel();
+	var signals = editor.signals;
+
+	var container = new UI.Row();
 
 
 	// vertices
 	// vertices
 
 
-	var verticesRow = new UI.Panel();
-	var vertices = new UI.Text().setFontSize( '12px' );
+	var verticesRow = new UI.Row();
+	var vertices = new UI.Text();
 
 
 	verticesRow.add( new UI.Text( 'Vertices' ).setWidth( '90px' ) );
 	verticesRow.add( new UI.Text( 'Vertices' ).setWidth( '90px' ) );
 	verticesRow.add( vertices );
 	verticesRow.add( vertices );
@@ -18,8 +20,8 @@ Sidebar.Geometry.Geometry = function ( signals ) {
 
 
 	// faces
 	// faces
 
 
-	var facesRow = new UI.Panel();
-	var faces = new UI.Text().setFontSize( '12px' );
+	var facesRow = new UI.Row();
+	var faces = new UI.Text();
 
 
 	facesRow.add( new UI.Text( 'Faces' ).setWidth( '90px' ) );
 	facesRow.add( new UI.Text( 'Faces' ).setWidth( '90px' ) );
 	facesRow.add( faces );
 	facesRow.add( faces );
@@ -34,7 +36,7 @@ Sidebar.Geometry.Geometry = function ( signals ) {
 
 
 		var geometry = object.geometry;
 		var geometry = object.geometry;
 
 
-		if ( geometry instanceof THREE.Geometry ) { 
+		if ( geometry instanceof THREE.Geometry ) {
 
 
 			container.setDisplay( 'block' );
 			container.setDisplay( 'block' );
 
 

+ 8 - 10
editor/js/Sidebar.Geometry.IcosahedronGeometry.js

@@ -2,15 +2,17 @@
  * @author mrdoob / http://mrdoob.com/
  * @author mrdoob / http://mrdoob.com/
  */
  */
 
 
-Sidebar.Geometry.IcosahedronGeometry = function ( signals, object ) {
+Sidebar.Geometry.IcosahedronGeometry = function ( editor, object ) {
 
 
-	var container = new UI.Panel();
+	var signals = editor.signals;
+
+	var container = new UI.Row();
 
 
 	var parameters = object.geometry.parameters;
 	var parameters = object.geometry.parameters;
 
 
 	// radius
 	// radius
 
 
-	var radiusRow = new UI.Panel();
+	var radiusRow = new UI.Row();
 	var radius = new UI.Number( parameters.radius ).onChange( update );
 	var radius = new UI.Number( parameters.radius ).onChange( update );
 
 
 	radiusRow.add( new UI.Text( 'Radius' ).setWidth( '90px' ) );
 	radiusRow.add( new UI.Text( 'Radius' ).setWidth( '90px' ) );
@@ -20,7 +22,7 @@ Sidebar.Geometry.IcosahedronGeometry = function ( signals, object ) {
 
 
 	// detail
 	// detail
 
 
-	var detailRow = new UI.Panel();
+	var detailRow = new UI.Row();
 	var detail = new UI.Integer( parameters.detail ).setRange( 0, Infinity ).onChange( update );
 	var detail = new UI.Integer( parameters.detail ).setRange( 0, Infinity ).onChange( update );
 
 
 	detailRow.add( new UI.Text( 'Detail' ).setWidth( '90px' ) );
 	detailRow.add( new UI.Text( 'Detail' ).setWidth( '90px' ) );
@@ -33,14 +35,10 @@ Sidebar.Geometry.IcosahedronGeometry = function ( signals, object ) {
 
 
 	function update() {
 	function update() {
 
 
-		object.geometry.dispose();
-
-		object.geometry = new THREE.IcosahedronGeometry(
+		editor.execute( new SetGeometryCommand( object, new THREE.IcosahedronGeometry(
 			radius.getValue(),
 			radius.getValue(),
 			detail.getValue()
 			detail.getValue()
-		);
-
-		object.geometry.computeBoundingSphere();
+		) ) );
 
 
 		signals.objectChanged.dispatch( object );
 		signals.objectChanged.dispatch( object );
 
 

+ 141 - 0
editor/js/Sidebar.Geometry.LatheGeometry.js

@@ -0,0 +1,141 @@
+/**
+ * @author rfm1201
+ */
+
+Sidebar.Geometry.LatheGeometry = function( editor, object ) {
+
+	var signals = editor.signals;
+
+	var container = new UI.Row();
+
+	var parameters = object.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() {
+
+		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 );
+
+		}
+
+		var geometry = new THREE.LatheGeometry(
+			points,
+			segments.getValue(),
+			phiStart.getValue() / 180 * Math.PI,
+			phiLength.getValue() / 180 * Math.PI
+		);
+
+		editor.execute( new SetGeometryCommand( object, geometry ) );
+
+	}
+
+	return container;
+
+};

+ 5 - 3
editor/js/Sidebar.Geometry.Modifiers.js

@@ -2,9 +2,11 @@
  * @author mrdoob / http://mrdoob.com/
  * @author mrdoob / http://mrdoob.com/
  */
  */
 
 
-Sidebar.Geometry.Modifiers = function ( signals, object ) {
+Sidebar.Geometry.Modifiers = function ( editor, object ) {
 
 
-	var container = new UI.Panel().setPaddingLeft( '90px' );
+	var signals = editor.signals;
+
+	var container = new UI.Row().setPaddingLeft( '90px' );
 
 
 	var geometry = object.geometry;
 	var geometry = object.geometry;
 
 
@@ -35,4 +37,4 @@ Sidebar.Geometry.Modifiers = function ( signals, object ) {
 
 
 	return container;
 	return container;
 
 
-}
+};

+ 10 - 14
editor/js/Sidebar.Geometry.PlaneGeometry.js

@@ -2,15 +2,17 @@
  * @author mrdoob / http://mrdoob.com/
  * @author mrdoob / http://mrdoob.com/
  */
  */
 
 
-Sidebar.Geometry.PlaneGeometry = function ( signals, object ) {
+Sidebar.Geometry.PlaneGeometry = function ( editor, object ) {
 
 
-	var container = new UI.Panel();
+	var signals = editor.signals;
+
+	var container = new UI.Row();
 
 
 	var parameters = object.geometry.parameters;
 	var parameters = object.geometry.parameters;
 
 
 	// width
 	// width
 
 
-	var widthRow = new UI.Panel();
+	var widthRow = new UI.Row();
 	var width = new UI.Number( parameters.width ).onChange( update );
 	var width = new UI.Number( parameters.width ).onChange( update );
 
 
 	widthRow.add( new UI.Text( 'Width' ).setWidth( '90px' ) );
 	widthRow.add( new UI.Text( 'Width' ).setWidth( '90px' ) );
@@ -20,7 +22,7 @@ Sidebar.Geometry.PlaneGeometry = function ( signals, object ) {
 
 
 	// height
 	// height
 
 
-	var heightRow = new UI.Panel();
+	var heightRow = new UI.Row();
 	var height = new UI.Number( parameters.height ).onChange( update );
 	var height = new UI.Number( parameters.height ).onChange( update );
 
 
 	heightRow.add( new UI.Text( 'Height' ).setWidth( '90px' ) );
 	heightRow.add( new UI.Text( 'Height' ).setWidth( '90px' ) );
@@ -30,7 +32,7 @@ Sidebar.Geometry.PlaneGeometry = function ( signals, object ) {
 
 
 	// widthSegments
 	// widthSegments
 
 
-	var widthSegmentsRow = new UI.Panel();
+	var widthSegmentsRow = new UI.Row();
 	var widthSegments = new UI.Integer( parameters.widthSegments ).setRange( 1, Infinity ).onChange( update );
 	var widthSegments = new UI.Integer( parameters.widthSegments ).setRange( 1, Infinity ).onChange( update );
 
 
 	widthSegmentsRow.add( new UI.Text( 'Width segments' ).setWidth( '90px' ) );
 	widthSegmentsRow.add( new UI.Text( 'Width segments' ).setWidth( '90px' ) );
@@ -40,7 +42,7 @@ Sidebar.Geometry.PlaneGeometry = function ( signals, object ) {
 
 
 	// heightSegments
 	// heightSegments
 
 
-	var heightSegmentsRow = new UI.Panel();
+	var heightSegmentsRow = new UI.Row();
 	var heightSegments = new UI.Integer( parameters.heightSegments ).setRange( 1, Infinity ).onChange( update );
 	var heightSegments = new UI.Integer( parameters.heightSegments ).setRange( 1, Infinity ).onChange( update );
 
 
 	heightSegmentsRow.add( new UI.Text( 'Height segments' ).setWidth( '90px' ) );
 	heightSegmentsRow.add( new UI.Text( 'Height segments' ).setWidth( '90px' ) );
@@ -52,19 +54,13 @@ Sidebar.Geometry.PlaneGeometry = function ( signals, object ) {
 	//
 	//
 
 
 	function update() {
 	function update() {
-		
-		object.geometry.dispose();
 
 
-		object.geometry = new THREE.PlaneGeometry(
+		editor.execute( new SetGeometryCommand( object, new THREE.PlaneGeometry(
 			width.getValue(),
 			width.getValue(),
 			height.getValue(),
 			height.getValue(),
 			widthSegments.getValue(),
 			widthSegments.getValue(),
 			heightSegments.getValue()
 			heightSegments.getValue()
-		);
-
-		object.geometry.computeBoundingSphere();
-
-		signals.geometryChanged.dispatch( object );
+		) ) );
 
 
 	}
 	}
 
 

+ 13 - 17
editor/js/Sidebar.Geometry.SphereGeometry.js

@@ -2,15 +2,17 @@
  * @author mrdoob / http://mrdoob.com/
  * @author mrdoob / http://mrdoob.com/
  */
  */
 
 
-Sidebar.Geometry.SphereGeometry = function ( signals, object ) {
+Sidebar.Geometry.SphereGeometry = function ( editor, object ) {
 
 
-	var container = new UI.Panel();
+	var signals = editor.signals;
+
+	var container = new UI.Row();
 
 
 	var parameters = object.geometry.parameters;
 	var parameters = object.geometry.parameters;
 
 
 	// radius
 	// radius
 
 
-	var radiusRow = new UI.Panel();
+	var radiusRow = new UI.Row();
 	var radius = new UI.Number( parameters.radius ).onChange( update );
 	var radius = new UI.Number( parameters.radius ).onChange( update );
 
 
 	radiusRow.add( new UI.Text( 'Radius' ).setWidth( '90px' ) );
 	radiusRow.add( new UI.Text( 'Radius' ).setWidth( '90px' ) );
@@ -20,7 +22,7 @@ Sidebar.Geometry.SphereGeometry = function ( signals, object ) {
 
 
 	// widthSegments
 	// widthSegments
 
 
-	var widthSegmentsRow = new UI.Panel();
+	var widthSegmentsRow = new UI.Row();
 	var widthSegments = new UI.Integer( parameters.widthSegments ).setRange( 1, Infinity ).onChange( update );
 	var widthSegments = new UI.Integer( parameters.widthSegments ).setRange( 1, Infinity ).onChange( update );
 
 
 	widthSegmentsRow.add( new UI.Text( 'Width segments' ).setWidth( '90px' ) );
 	widthSegmentsRow.add( new UI.Text( 'Width segments' ).setWidth( '90px' ) );
@@ -30,7 +32,7 @@ Sidebar.Geometry.SphereGeometry = function ( signals, object ) {
 
 
 	// heightSegments
 	// heightSegments
 
 
-	var heightSegmentsRow = new UI.Panel();
+	var heightSegmentsRow = new UI.Row();
 	var heightSegments = new UI.Integer( parameters.heightSegments ).setRange( 1, Infinity ).onChange( update );
 	var heightSegments = new UI.Integer( parameters.heightSegments ).setRange( 1, Infinity ).onChange( update );
 
 
 	heightSegmentsRow.add( new UI.Text( 'Height segments' ).setWidth( '90px' ) );
 	heightSegmentsRow.add( new UI.Text( 'Height segments' ).setWidth( '90px' ) );
@@ -40,7 +42,7 @@ Sidebar.Geometry.SphereGeometry = function ( signals, object ) {
 
 
 	// phiStart
 	// phiStart
 
 
-	var phiStartRow = new UI.Panel();
+	var phiStartRow = new UI.Row();
 	var phiStart = new UI.Number( parameters.phiStart ).onChange( update );
 	var phiStart = new UI.Number( parameters.phiStart ).onChange( update );
 
 
 	phiStartRow.add( new UI.Text( 'Phi start' ).setWidth( '90px' ) );
 	phiStartRow.add( new UI.Text( 'Phi start' ).setWidth( '90px' ) );
@@ -50,7 +52,7 @@ Sidebar.Geometry.SphereGeometry = function ( signals, object ) {
 
 
 	// phiLength
 	// phiLength
 
 
-	var phiLengthRow = new UI.Panel();
+	var phiLengthRow = new UI.Row();
 	var phiLength = new UI.Number( parameters.phiLength ).onChange( update );
 	var phiLength = new UI.Number( parameters.phiLength ).onChange( update );
 
 
 	phiLengthRow.add( new UI.Text( 'Phi length' ).setWidth( '90px' ) );
 	phiLengthRow.add( new UI.Text( 'Phi length' ).setWidth( '90px' ) );
@@ -60,7 +62,7 @@ Sidebar.Geometry.SphereGeometry = function ( signals, object ) {
 
 
 	// thetaStart
 	// thetaStart
 
 
-	var thetaStartRow = new UI.Panel();
+	var thetaStartRow = new UI.Row();
 	var thetaStart = new UI.Number( parameters.thetaStart ).onChange( update );
 	var thetaStart = new UI.Number( parameters.thetaStart ).onChange( update );
 
 
 	thetaStartRow.add( new UI.Text( 'Theta start' ).setWidth( '90px' ) );
 	thetaStartRow.add( new UI.Text( 'Theta start' ).setWidth( '90px' ) );
@@ -70,7 +72,7 @@ Sidebar.Geometry.SphereGeometry = function ( signals, object ) {
 
 
 	// thetaLength
 	// thetaLength
 
 
-	var thetaLengthRow = new UI.Panel();
+	var thetaLengthRow = new UI.Row();
 	var thetaLength = new UI.Number( parameters.thetaLength ).onChange( update );
 	var thetaLength = new UI.Number( parameters.thetaLength ).onChange( update );
 
 
 	thetaLengthRow.add( new UI.Text( 'Theta length' ).setWidth( '90px' ) );
 	thetaLengthRow.add( new UI.Text( 'Theta length' ).setWidth( '90px' ) );
@@ -83,9 +85,7 @@ Sidebar.Geometry.SphereGeometry = function ( signals, object ) {
 
 
 	function update() {
 	function update() {
 
 
-		object.geometry.dispose();
-
-		object.geometry = new THREE.SphereGeometry(
+		editor.execute( new SetGeometryCommand( object, new THREE.SphereGeometry(
 			radius.getValue(),
 			radius.getValue(),
 			widthSegments.getValue(),
 			widthSegments.getValue(),
 			heightSegments.getValue(),
 			heightSegments.getValue(),
@@ -93,11 +93,7 @@ Sidebar.Geometry.SphereGeometry = function ( signals, object ) {
 			phiLength.getValue(),
 			phiLength.getValue(),
 			thetaStart.getValue(),
 			thetaStart.getValue(),
 			thetaLength.getValue()
 			thetaLength.getValue()
-		);
-
-		object.geometry.computeBoundingSphere();
-
-		signals.geometryChanged.dispatch( object );
+		) ) );
 
 
 	}
 	}
 
 

+ 8 - 8
editor/js/Sidebar.Geometry.TeapotBufferGeometry.js

@@ -4,13 +4,13 @@
 
 
 Sidebar.Geometry.TeapotBufferGeometry = function ( signals, object ) {
 Sidebar.Geometry.TeapotBufferGeometry = function ( signals, object ) {
 
 
-	var container = new UI.Panel();
+	var container = new UI.Row();
 
 
 	var parameters = object.geometry.parameters;
 	var parameters = object.geometry.parameters;
 
 
 	// size
 	// size
 
 
-	var sizeRow = new UI.Panel();
+	var sizeRow = new UI.Row();
 	var size = new UI.Number( parameters.size ).onChange( update );
 	var size = new UI.Number( parameters.size ).onChange( update );
 
 
 	sizeRow.add( new UI.Text( 'Size' ).setWidth( '90px' ) );
 	sizeRow.add( new UI.Text( 'Size' ).setWidth( '90px' ) );
@@ -20,7 +20,7 @@ Sidebar.Geometry.TeapotBufferGeometry = function ( signals, object ) {
 
 
 	// segments
 	// segments
 
 
-	var segmentsRow = new UI.Panel();
+	var segmentsRow = new UI.Row();
 	var segments = new UI.Integer( parameters.segments ).setRange( 1, Infinity ).onChange( update );
 	var segments = new UI.Integer( parameters.segments ).setRange( 1, Infinity ).onChange( update );
 
 
 	segmentsRow.add( new UI.Text( 'Segments' ).setWidth( '90px' ) );
 	segmentsRow.add( new UI.Text( 'Segments' ).setWidth( '90px' ) );
@@ -30,7 +30,7 @@ Sidebar.Geometry.TeapotBufferGeometry = function ( signals, object ) {
 
 
 	// bottom
 	// bottom
 
 
-	var bottomRow = new UI.Panel();
+	var bottomRow = new UI.Row();
 	var bottom = new UI.Checkbox( parameters.bottom ).onChange( update );
 	var bottom = new UI.Checkbox( parameters.bottom ).onChange( update );
 
 
 	bottomRow.add( new UI.Text( 'Bottom' ).setWidth( '90px' ) );
 	bottomRow.add( new UI.Text( 'Bottom' ).setWidth( '90px' ) );
@@ -40,7 +40,7 @@ Sidebar.Geometry.TeapotBufferGeometry = function ( signals, object ) {
 
 
 	// lid
 	// lid
 
 
-	var lidRow = new UI.Panel();
+	var lidRow = new UI.Row();
 	var lid = new UI.Checkbox( parameters.lid ).onChange( update );
 	var lid = new UI.Checkbox( parameters.lid ).onChange( update );
 
 
 	lidRow.add( new UI.Text( 'Lid' ).setWidth( '90px' ) );
 	lidRow.add( new UI.Text( 'Lid' ).setWidth( '90px' ) );
@@ -50,7 +50,7 @@ Sidebar.Geometry.TeapotBufferGeometry = function ( signals, object ) {
 
 
 	// body
 	// body
 
 
-	var bodyRow = new UI.Panel();
+	var bodyRow = new UI.Row();
 	var body = new UI.Checkbox( parameters.body ).onChange( update );
 	var body = new UI.Checkbox( parameters.body ).onChange( update );
 
 
 	bodyRow.add( new UI.Text( 'Body' ).setWidth( '90px' ) );
 	bodyRow.add( new UI.Text( 'Body' ).setWidth( '90px' ) );
@@ -60,7 +60,7 @@ Sidebar.Geometry.TeapotBufferGeometry = function ( signals, object ) {
 
 
 	// fitted lid
 	// fitted lid
 
 
-	var fitLidRow = new UI.Panel();
+	var fitLidRow = new UI.Row();
 	var fitLid = new UI.Checkbox( parameters.fitLid ).onChange( update );
 	var fitLid = new UI.Checkbox( parameters.fitLid ).onChange( update );
 
 
 	fitLidRow.add( new UI.Text( 'Fitted Lid' ).setWidth( '90px' ) );
 	fitLidRow.add( new UI.Text( 'Fitted Lid' ).setWidth( '90px' ) );
@@ -70,7 +70,7 @@ Sidebar.Geometry.TeapotBufferGeometry = function ( signals, object ) {
 
 
 	// blinn-sized
 	// blinn-sized
 
 
-	var blinnRow = new UI.Panel();
+	var blinnRow = new UI.Row();
 	var blinn = new UI.Checkbox( parameters.blinn ).onChange( update );
 	var blinn = new UI.Checkbox( parameters.blinn ).onChange( update );
 
 
 	blinnRow.add( new UI.Text( 'Blinn-scaled' ).setWidth( '90px' ) );
 	blinnRow.add( new UI.Text( 'Blinn-scaled' ).setWidth( '90px' ) );

+ 11 - 15
editor/js/Sidebar.Geometry.TorusGeometry.js

@@ -2,15 +2,17 @@
  * @author mrdoob / http://mrdoob.com/
  * @author mrdoob / http://mrdoob.com/
  */
  */
 
 
-Sidebar.Geometry.TorusGeometry = function ( signals, object ) {
+Sidebar.Geometry.TorusGeometry = function ( editor, object ) {
 
 
-	var container = new UI.Panel();
+	var signals = editor.signals;
+
+	var container = new UI.Row();
 
 
 	var parameters = object.geometry.parameters;
 	var parameters = object.geometry.parameters;
 
 
 	// radius
 	// radius
 
 
-	var radiusRow = new UI.Panel();
+	var radiusRow = new UI.Row();
 	var radius = new UI.Number( parameters.radius ).onChange( update );
 	var radius = new UI.Number( parameters.radius ).onChange( update );
 
 
 	radiusRow.add( new UI.Text( 'Radius' ).setWidth( '90px' ) );
 	radiusRow.add( new UI.Text( 'Radius' ).setWidth( '90px' ) );
@@ -20,7 +22,7 @@ Sidebar.Geometry.TorusGeometry = function ( signals, object ) {
 
 
 	// tube
 	// tube
 
 
-	var tubeRow = new UI.Panel();
+	var tubeRow = new UI.Row();
 	var tube = new UI.Number( parameters.tube ).onChange( update );
 	var tube = new UI.Number( parameters.tube ).onChange( update );
 
 
 	tubeRow.add( new UI.Text( 'Tube' ).setWidth( '90px' ) );
 	tubeRow.add( new UI.Text( 'Tube' ).setWidth( '90px' ) );
@@ -30,7 +32,7 @@ Sidebar.Geometry.TorusGeometry = function ( signals, object ) {
 
 
 	// radialSegments
 	// radialSegments
 
 
-	var radialSegmentsRow = new UI.Panel();
+	var radialSegmentsRow = new UI.Row();
 	var radialSegments = new UI.Integer( parameters.radialSegments ).setRange( 1, Infinity ).onChange( update );
 	var radialSegments = new UI.Integer( parameters.radialSegments ).setRange( 1, Infinity ).onChange( update );
 
 
 	radialSegmentsRow.add( new UI.Text( 'Radial segments' ).setWidth( '90px' ) );
 	radialSegmentsRow.add( new UI.Text( 'Radial segments' ).setWidth( '90px' ) );
@@ -40,7 +42,7 @@ Sidebar.Geometry.TorusGeometry = function ( signals, object ) {
 
 
 	// tubularSegments
 	// tubularSegments
 
 
-	var tubularSegmentsRow = new UI.Panel();
+	var tubularSegmentsRow = new UI.Row();
 	var tubularSegments = new UI.Integer( parameters.tubularSegments ).setRange( 1, Infinity ).onChange( update );
 	var tubularSegments = new UI.Integer( parameters.tubularSegments ).setRange( 1, Infinity ).onChange( update );
 
 
 	tubularSegmentsRow.add( new UI.Text( 'Tubular segments' ).setWidth( '90px' ) );
 	tubularSegmentsRow.add( new UI.Text( 'Tubular segments' ).setWidth( '90px' ) );
@@ -50,7 +52,7 @@ Sidebar.Geometry.TorusGeometry = function ( signals, object ) {
 
 
 	// arc
 	// arc
 
 
-	var arcRow = new UI.Panel();
+	var arcRow = new UI.Row();
 	var arc = new UI.Number( parameters.arc ).onChange( update );
 	var arc = new UI.Number( parameters.arc ).onChange( update );
 
 
 	arcRow.add( new UI.Text( 'Arc' ).setWidth( '90px' ) );
 	arcRow.add( new UI.Text( 'Arc' ).setWidth( '90px' ) );
@@ -63,19 +65,13 @@ Sidebar.Geometry.TorusGeometry = function ( signals, object ) {
 
 
 	function update() {
 	function update() {
 
 
-		object.geometry.dispose();
-
-		object.geometry = new THREE.TorusGeometry(
+		editor.execute( new SetGeometryCommand( object, new THREE.TorusGeometry(
 			radius.getValue(),
 			radius.getValue(),
 			tube.getValue(),
 			tube.getValue(),
 			radialSegments.getValue(),
 			radialSegments.getValue(),
 			tubularSegments.getValue(),
 			tubularSegments.getValue(),
 			arc.getValue()
 			arc.getValue()
-		);
-
-		object.geometry.computeBoundingSphere();
-
-		signals.geometryChanged.dispatch( object );
+		) ) );
 
 
 	}
 	}
 
 

+ 13 - 17
editor/js/Sidebar.Geometry.TorusKnotGeometry.js

@@ -2,15 +2,17 @@
  * @author mrdoob / http://mrdoob.com/
  * @author mrdoob / http://mrdoob.com/
  */
  */
 
 
-Sidebar.Geometry.TorusKnotGeometry = function ( signals, object ) {
+Sidebar.Geometry.TorusKnotGeometry = function ( editor, object ) {
 
 
-	var container = new UI.Panel();
+	var signals = editor.signals;
+
+	var container = new UI.Row();
 
 
 	var parameters = object.geometry.parameters;
 	var parameters = object.geometry.parameters;
 
 
 	// radius
 	// radius
 
 
-	var radiusRow = new UI.Panel();
+	var radiusRow = new UI.Row();
 	var radius = new UI.Number( parameters.radius ).onChange( update );
 	var radius = new UI.Number( parameters.radius ).onChange( update );
 
 
 	radiusRow.add( new UI.Text( 'Radius' ).setWidth( '90px' ) );
 	radiusRow.add( new UI.Text( 'Radius' ).setWidth( '90px' ) );
@@ -20,7 +22,7 @@ Sidebar.Geometry.TorusKnotGeometry = function ( signals, object ) {
 
 
 	// tube
 	// tube
 
 
-	var tubeRow = new UI.Panel();
+	var tubeRow = new UI.Row();
 	var tube = new UI.Number( parameters.tube ).onChange( update );
 	var tube = new UI.Number( parameters.tube ).onChange( update );
 
 
 	tubeRow.add( new UI.Text( 'Tube' ).setWidth( '90px' ) );
 	tubeRow.add( new UI.Text( 'Tube' ).setWidth( '90px' ) );
@@ -30,7 +32,7 @@ Sidebar.Geometry.TorusKnotGeometry = function ( signals, object ) {
 
 
 	// radialSegments
 	// radialSegments
 
 
-	var radialSegmentsRow = new UI.Panel();
+	var radialSegmentsRow = new UI.Row();
 	var radialSegments = new UI.Integer( parameters.radialSegments ).setRange( 1, Infinity ).onChange( update );
 	var radialSegments = new UI.Integer( parameters.radialSegments ).setRange( 1, Infinity ).onChange( update );
 
 
 	radialSegmentsRow.add( new UI.Text( 'Radial segments' ).setWidth( '90px' ) );
 	radialSegmentsRow.add( new UI.Text( 'Radial segments' ).setWidth( '90px' ) );
@@ -40,7 +42,7 @@ Sidebar.Geometry.TorusKnotGeometry = function ( signals, object ) {
 
 
 	// tubularSegments
 	// tubularSegments
 
 
-	var tubularSegmentsRow = new UI.Panel();
+	var tubularSegmentsRow = new UI.Row();
 	var tubularSegments = new UI.Integer( parameters.tubularSegments ).setRange( 1, Infinity ).onChange( update );
 	var tubularSegments = new UI.Integer( parameters.tubularSegments ).setRange( 1, Infinity ).onChange( update );
 
 
 	tubularSegmentsRow.add( new UI.Text( 'Tubular segments' ).setWidth( '90px' ) );
 	tubularSegmentsRow.add( new UI.Text( 'Tubular segments' ).setWidth( '90px' ) );
@@ -50,7 +52,7 @@ Sidebar.Geometry.TorusKnotGeometry = function ( signals, object ) {
 
 
 	// p
 	// p
 
 
-	var pRow = new UI.Panel();
+	var pRow = new UI.Row();
 	var p = new UI.Number( parameters.p ).onChange( update );
 	var p = new UI.Number( parameters.p ).onChange( update );
 
 
 	pRow.add( new UI.Text( 'P' ).setWidth( '90px' ) );
 	pRow.add( new UI.Text( 'P' ).setWidth( '90px' ) );
@@ -60,7 +62,7 @@ Sidebar.Geometry.TorusKnotGeometry = function ( signals, object ) {
 
 
 	// q
 	// q
 
 
-	var qRow = new UI.Panel();
+	var qRow = new UI.Row();
 	var q = new UI.Number( parameters.q ).onChange( update );
 	var q = new UI.Number( parameters.q ).onChange( update );
 
 
 	pRow.add( new UI.Text( 'Q' ).setWidth( '90px' ) );
 	pRow.add( new UI.Text( 'Q' ).setWidth( '90px' ) );
@@ -70,7 +72,7 @@ Sidebar.Geometry.TorusKnotGeometry = function ( signals, object ) {
 
 
 	// heightScale
 	// heightScale
 
 
-	var heightScaleRow = new UI.Panel();
+	var heightScaleRow = new UI.Row();
 	var heightScale = new UI.Number( parameters.heightScale ).onChange( update );
 	var heightScale = new UI.Number( parameters.heightScale ).onChange( update );
 
 
 	pRow.add( new UI.Text( 'Height scale' ).setWidth( '90px' ) );
 	pRow.add( new UI.Text( 'Height scale' ).setWidth( '90px' ) );
@@ -83,9 +85,7 @@ Sidebar.Geometry.TorusKnotGeometry = function ( signals, object ) {
 
 
 	function update() {
 	function update() {
 
 
-		object.geometry.dispose();
-
-		object.geometry = new THREE.TorusKnotGeometry(
+		editor.execute( new SetGeometryCommand( object, new THREE.TorusKnotGeometry(
 			radius.getValue(),
 			radius.getValue(),
 			tube.getValue(),
 			tube.getValue(),
 			radialSegments.getValue(),
 			radialSegments.getValue(),
@@ -93,11 +93,7 @@ Sidebar.Geometry.TorusKnotGeometry = function ( signals, object ) {
 			p.getValue(),
 			p.getValue(),
 			q.getValue(),
 			q.getValue(),
 			heightScale.getValue()
 			heightScale.getValue()
-		);
-
-		object.geometry.computeBoundingSphere();
-
-		signals.geometryChanged.dispatch( object );
+		) ) );
 
 
 	}
 	}
 
 

+ 37 - 38
editor/js/Sidebar.Geometry.js

@@ -6,21 +6,13 @@ Sidebar.Geometry = function ( editor ) {
 
 
 	var signals = editor.signals;
 	var signals = editor.signals;
 
 
-	var container = new UI.CollapsiblePanel();
-	container.setCollapsed( editor.config.getKey( 'ui/sidebar/geometry/collapsed' ) );
-	container.onCollapsedChange( function ( boolean ) {
-
-		editor.config.setKey( 'ui/sidebar/geometry/collapsed', boolean );
-
-	} );
-	container.setDisplay( 'none' );
-
-	var geometryType = new UI.Text().setTextTransform( 'uppercase' );
-	container.addStatic( geometryType );
+	var container = new UI.Panel();
+	container.setBorderTop( '0' );
+	container.setPaddingTop( '20px' );
 
 
 	// Actions
 	// Actions
 
 
-	var objectActions = new UI.Select().setPosition('absolute').setRight( '8px' ).setFontSize( '11px' );
+	var objectActions = new UI.Select().setPosition( 'absolute' ).setRight( '8px' ).setFontSize( '11px' );
 	objectActions.setOptions( {
 	objectActions.setOptions( {
 
 
 		'Actions': 'Actions',
 		'Actions': 'Actions',
@@ -49,10 +41,11 @@ Sidebar.Geometry = function ( editor ) {
 
 
 				var offset = geometry.center();
 				var offset = geometry.center();
 
 
-				object.position.sub( offset );
+				var newPosition = object.position.clone();
+				newPosition.sub( offset );
+				editor.execute( new SetPositionCommand( object, newPosition ) );
 
 
-				editor.signals.geometryChanged.dispatch( geometry );
-				editor.signals.objectChanged.dispatch( object );
+				editor.signals.geometryChanged.dispatch( object );
 
 
 				break;
 				break;
 
 
@@ -60,9 +53,7 @@ Sidebar.Geometry = function ( editor ) {
 
 
 				if ( geometry instanceof THREE.Geometry ) {
 				if ( geometry instanceof THREE.Geometry ) {
 
 
-					object.geometry = new THREE.BufferGeometry().fromGeometry( geometry );
-
-					signals.geometryChanged.dispatch( object );
+					editor.execute( new SetGeometryCommand( object, new THREE.BufferGeometry().fromGeometry( geometry ) ) );
 
 
 				}
 				}
 
 
@@ -70,14 +61,16 @@ Sidebar.Geometry = function ( editor ) {
 
 
 			case 'Flatten':
 			case 'Flatten':
 
 
-				geometry.applyMatrix( object.matrix );
+				var newGeometry = geometry.clone();
+				newGeometry.uuid = geometry.uuid;
+				newGeometry.applyMatrix( object.matrix );
 
 
-				object.position.set( 0, 0, 0 );
-				object.rotation.set( 0, 0, 0 );
-				object.scale.set( 1, 1, 1 );
+				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.signals.geometryChanged.dispatch( geometry );
-				editor.signals.objectChanged.dispatch( object );
+				editor.execute( new MultiCmdsCommand( cmds ), 'Flatten Geometry' );
 
 
 				break;
 				break;
 
 
@@ -85,22 +78,28 @@ Sidebar.Geometry = function ( editor ) {
 
 
 		this.setValue( 'Actions' );
 		this.setValue( 'Actions' );
 
 
-		signals.objectChanged.dispatch( object );
-
 	} );
 	} );
-	container.addStatic( objectActions );
+	// 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( new UI.Break() );
+	container.add( geometryTypeRow );
 
 
 	// uuid
 	// uuid
 
 
-	var geometryUUIDRow = new UI.Panel();
+	var geometryUUIDRow = new UI.Row();
 	var geometryUUID = new UI.Input().setWidth( '115px' ).setFontSize( '12px' ).setDisabled( true );
 	var geometryUUID = new UI.Input().setWidth( '115px' ).setFontSize( '12px' ).setDisabled( true );
 	var geometryUUIDRenew = new UI.Button( '⟳' ).setMarginLeft( '7px' ).onClick( function () {
 	var geometryUUIDRenew = new UI.Button( '⟳' ).setMarginLeft( '7px' ).onClick( function () {
 
 
 		geometryUUID.setValue( THREE.Math.generateUUID() );
 		geometryUUID.setValue( THREE.Math.generateUUID() );
 
 
-		editor.selected.geometry.uuid = geometryUUID.getValue();
+		editor.execute( new SetGeometryValueCommand( editor.selected, 'uuid', geometryUUID.getValue() ) );
 
 
 	} );
 	} );
 
 
@@ -112,10 +111,10 @@ Sidebar.Geometry = function ( editor ) {
 
 
 	// name
 	// name
 
 
-	var geometryNameRow = new UI.Panel();
+	var geometryNameRow = new UI.Row();
 	var geometryName = new UI.Input().setWidth( '150px' ).setFontSize( '12px' ).onChange( function () {
 	var geometryName = new UI.Input().setWidth( '150px' ).setFontSize( '12px' ).onChange( function () {
 
 
-		editor.setGeometryName( editor.selected.geometry, geometryName.getValue() );
+		editor.execute( new SetGeometryValueCommand( editor.selected, 'name', geometryName.getValue() ) );
 
 
 	} );
 	} );
 
 
@@ -126,15 +125,15 @@ Sidebar.Geometry = function ( editor ) {
 
 
 	// geometry
 	// geometry
 
 
-	container.add( new Sidebar.Geometry.Geometry( signals ) );
+	container.add( new Sidebar.Geometry.Geometry( editor ) );
 
 
 	// buffergeometry
 	// buffergeometry
 
 
-	container.add( new Sidebar.Geometry.BufferGeometry( signals ) );
+	container.add( new Sidebar.Geometry.BufferGeometry( editor ) );
 
 
 	// parameters
 	// parameters
 
 
-	var parameters = new UI.Panel();
+	var parameters = new UI.Span();
 	container.add( parameters );
 	container.add( parameters );
 
 
 
 
@@ -161,11 +160,11 @@ Sidebar.Geometry = function ( editor ) {
 
 
 			if ( geometry.type === 'BufferGeometry' || geometry.type === 'Geometry' ) {
 			if ( geometry.type === 'BufferGeometry' || geometry.type === 'Geometry' ) {
 
 
-				parameters.add( new Sidebar.Geometry.Modifiers( signals, object ) );
+				parameters.add( new Sidebar.Geometry.Modifiers( editor, object ) );
 
 
 			} else if ( Sidebar.Geometry[ geometry.type ] !== undefined ) {
 			} else if ( Sidebar.Geometry[ geometry.type ] !== undefined ) {
 
 
-				parameters.add( new Sidebar.Geometry[ geometry.type ]( signals, object ) );
+				parameters.add( new Sidebar.Geometry[ geometry.type ]( editor, object ) );
 
 
 			}
 			}
 
 
@@ -182,4 +181,4 @@ Sidebar.Geometry = function ( editor ) {
 
 
 	return container;
 	return container;
 
 
-}
+};

+ 117 - 0
editor/js/Sidebar.History.js

@@ -0,0 +1,117 @@
+/**
+ * @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 addObjects( objects, pad ) {
+
+			for ( var i = 0, l = objects.length; i < l; i ++ ) {
+
+				var object = objects[ i ];
+
+				var html = pad + "<span style='color: #0000cc '>" + enumerator ++ + ". Undo: " + object.name + "</span>";
+
+				options.push( { value: object.id, html: html } );
+
+			}
+
+		} )( history.undos, '&nbsp;' );
+
+
+		( function addObjects( objects, pad ) {
+
+			for ( var i = objects.length - 1; i >= 0; i -- ) {
+
+				var object = objects[ i ];
+
+				var html = pad + "<span style='color: #71544e'>" + enumerator ++ + ". Redo: " +  object.name + "</span>";
+
+				options.push( { value: object.id, html: html } );
+
+			}
+
+		} )( 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;
+
+};

+ 385 - 118
editor/js/Sidebar.Material.js

@@ -7,22 +7,36 @@ Sidebar.Material = function ( editor ) {
 	var signals = editor.signals;
 	var signals = editor.signals;
 	var currentObject;
 	var currentObject;
 
 
-	var container = new UI.CollapsiblePanel();
-	container.setCollapsed( editor.config.getKey( 'ui/sidebar/material/collapsed' ) );
-	container.onCollapsedChange( function ( boolean ) {
+	var container = new UI.Panel();
+	container.setBorderTop( '0' );
+	container.setPaddingTop( '20px' );
 
 
-		editor.config.setKey( 'ui/sidebar/material/collapsed', boolean );
+	// type
 
 
-	} );
-	container.setDisplay( 'none' );
-	container.dom.classList.add( 'Material' );
+	var materialClassRow = new UI.Row();
+	var materialClass = new UI.Select().setOptions( {
 
 
-	container.addStatic( new UI.Text().setValue( 'MATERIAL' ) );
-	container.add( new UI.Break() );
+		'LineBasicMaterial': 'LineBasicMaterial',
+		'LineDashedMaterial': 'LineDashedMaterial',
+		'MeshBasicMaterial': 'MeshBasicMaterial',
+		'MeshDepthMaterial': 'MeshDepthMaterial',
+		'MeshNormalMaterial': 'MeshNormalMaterial',
+		'MeshLambertMaterial': 'MeshLambertMaterial',
+		'MeshPhongMaterial': 'MeshPhongMaterial',
+		'MeshStandardMaterial': 'MeshStandardMaterial',
+		'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
 	// uuid
 
 
-	var materialUUIDRow = new UI.Panel();
+	var materialUUIDRow = new UI.Row();
 	var materialUUID = new UI.Input().setWidth( '115px' ).setFontSize( '12px' ).setDisabled( true );
 	var materialUUID = new UI.Input().setWidth( '115px' ).setFontSize( '12px' ).setDisabled( true );
 	var materialUUIDRenew = new UI.Button( '⟳' ).setMarginLeft( '7px' ).onClick( function () {
 	var materialUUIDRenew = new UI.Button( '⟳' ).setMarginLeft( '7px' ).onClick( function () {
 
 
@@ -39,10 +53,10 @@ Sidebar.Material = function ( editor ) {
 
 
 	// name
 	// name
 
 
-	var materialNameRow = new UI.Panel();
+	var materialNameRow = new UI.Row();
 	var materialName = new UI.Input().setWidth( '150px' ).setFontSize( '12px' ).onChange( function () {
 	var materialName = new UI.Input().setWidth( '150px' ).setFontSize( '12px' ).onChange( function () {
 
 
-		editor.setMaterialName( editor.selected.material, materialName.getValue() );
+		editor.execute( new SetMaterialValueCommand( editor.selected, 'name', materialName.getValue() ) );
 
 
 	} );
 	} );
 
 
@@ -51,38 +65,16 @@ Sidebar.Material = function ( editor ) {
 
 
 	container.add( materialNameRow );
 	container.add( materialNameRow );
 
 
-	// class
-
-	var materialClassRow = new UI.Panel();
-	var materialClass = new UI.Select().setOptions( {
-
-		'LineBasicMaterial': 'LineBasicMaterial',
-		'LineDashedMaterial': 'LineDashedMaterial',
-		'MeshBasicMaterial': 'MeshBasicMaterial',
-		'MeshDepthMaterial': 'MeshDepthMaterial',
-		'MeshLambertMaterial': 'MeshLambertMaterial',
-		'MeshNormalMaterial': 'MeshNormalMaterial',
-		'MeshPhongMaterial': 'MeshPhongMaterial',
-		'ShaderMaterial': 'ShaderMaterial',
-		'SpriteMaterial': 'SpriteMaterial'
-
-	} ).setWidth( '150px' ).setFontSize( '12px' ).onChange( update );
-
-	materialClassRow.add( new UI.Text( 'Type' ).setWidth( '90px' ) );
-	materialClassRow.add( materialClass );
-
-	container.add( materialClassRow );
-
 	// program
 	// program
 
 
-	var materialProgramRow = new UI.Panel();
+	var materialProgramRow = new UI.Row();
 	materialProgramRow.add( new UI.Text( 'Program' ).setWidth( '90px' ) );
 	materialProgramRow.add( new UI.Text( 'Program' ).setWidth( '90px' ) );
 
 
 	var materialProgramInfo = new UI.Button( 'Info' );
 	var materialProgramInfo = new UI.Button( 'Info' );
 	materialProgramInfo.setMarginLeft( '4px' );
 	materialProgramInfo.setMarginLeft( '4px' );
 	materialProgramInfo.onClick( function () {
 	materialProgramInfo.onClick( function () {
 
 
-		signals.editScript.dispatch( currentObject.material, 'programInfo' );
+		signals.editScript.dispatch( currentObject, 'programInfo' );
 
 
 	} );
 	} );
 	materialProgramRow.add( materialProgramInfo );
 	materialProgramRow.add( materialProgramInfo );
@@ -91,7 +83,7 @@ Sidebar.Material = function ( editor ) {
 	materialProgramVertex.setMarginLeft( '4px' );
 	materialProgramVertex.setMarginLeft( '4px' );
 	materialProgramVertex.onClick( function () {
 	materialProgramVertex.onClick( function () {
 
 
-		signals.editScript.dispatch( currentObject.material, 'vertexShader' );
+		signals.editScript.dispatch( currentObject, 'vertexShader' );
 
 
 	} );
 	} );
 	materialProgramRow.add( materialProgramVertex );
 	materialProgramRow.add( materialProgramVertex );
@@ -100,7 +92,7 @@ Sidebar.Material = function ( editor ) {
 	materialProgramFragment.setMarginLeft( '4px' );
 	materialProgramFragment.setMarginLeft( '4px' );
 	materialProgramFragment.onClick( function () {
 	materialProgramFragment.onClick( function () {
 
 
-		signals.editScript.dispatch( currentObject.material, 'fragmentShader' );
+		signals.editScript.dispatch( currentObject, 'fragmentShader' );
 
 
 	} );
 	} );
 	materialProgramRow.add( materialProgramFragment );
 	materialProgramRow.add( materialProgramFragment );
@@ -109,7 +101,7 @@ Sidebar.Material = function ( editor ) {
 
 
 	// color
 	// color
 
 
-	var materialColorRow = new UI.Panel();
+	var materialColorRow = new UI.Row();
 	var materialColor = new UI.Color().onChange( update );
 	var materialColor = new UI.Color().onChange( update );
 
 
 	materialColorRow.add( new UI.Text( 'Color' ).setWidth( '90px' ) );
 	materialColorRow.add( new UI.Text( 'Color' ).setWidth( '90px' ) );
@@ -117,9 +109,29 @@ Sidebar.Material = function ( editor ) {
 
 
 	container.add( materialColorRow );
 	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
 	// emissive
 
 
-	var materialEmissiveRow = new UI.Panel();
+	var materialEmissiveRow = new UI.Row();
 	var materialEmissive = new UI.Color().setHexValue( 0x000000 ).onChange( update );
 	var materialEmissive = new UI.Color().setHexValue( 0x000000 ).onChange( update );
 
 
 	materialEmissiveRow.add( new UI.Text( 'Emissive' ).setWidth( '90px' ) );
 	materialEmissiveRow.add( new UI.Text( 'Emissive' ).setWidth( '90px' ) );
@@ -129,7 +141,7 @@ Sidebar.Material = function ( editor ) {
 
 
 	// specular
 	// specular
 
 
-	var materialSpecularRow = new UI.Panel();
+	var materialSpecularRow = new UI.Row();
 	var materialSpecular = new UI.Color().setHexValue( 0x111111 ).onChange( update );
 	var materialSpecular = new UI.Color().setHexValue( 0x111111 ).onChange( update );
 
 
 	materialSpecularRow.add( new UI.Text( 'Specular' ).setWidth( '90px' ) );
 	materialSpecularRow.add( new UI.Text( 'Specular' ).setWidth( '90px' ) );
@@ -139,7 +151,7 @@ Sidebar.Material = function ( editor ) {
 
 
 	// shininess
 	// shininess
 
 
-	var materialShininessRow = new UI.Panel();
+	var materialShininessRow = new UI.Row();
 	var materialShininess = new UI.Number( 30 ).onChange( update );
 	var materialShininess = new UI.Number( 30 ).onChange( update );
 
 
 	materialShininessRow.add( new UI.Text( 'Shininess' ).setWidth( '90px' ) );
 	materialShininessRow.add( new UI.Text( 'Shininess' ).setWidth( '90px' ) );
@@ -149,7 +161,7 @@ Sidebar.Material = function ( editor ) {
 
 
 	// vertex colors
 	// vertex colors
 
 
-	var materialVertexColorsRow = new UI.Panel();
+	var materialVertexColorsRow = new UI.Row();
 	var materialVertexColors = new UI.Select().setOptions( {
 	var materialVertexColors = new UI.Select().setOptions( {
 
 
 		0: 'No',
 		0: 'No',
@@ -165,7 +177,7 @@ Sidebar.Material = function ( editor ) {
 
 
 	// skinning
 	// skinning
 
 
-	var materialSkinningRow = new UI.Panel();
+	var materialSkinningRow = new UI.Row();
 	var materialSkinning = new UI.Checkbox( false ).onChange( update );
 	var materialSkinning = new UI.Checkbox( false ).onChange( update );
 
 
 	materialSkinningRow.add( new UI.Text( 'Skinning' ).setWidth( '90px' ) );
 	materialSkinningRow.add( new UI.Text( 'Skinning' ).setWidth( '90px' ) );
@@ -175,7 +187,7 @@ Sidebar.Material = function ( editor ) {
 
 
 	// map
 	// map
 
 
-	var materialMapRow = new UI.Panel();
+	var materialMapRow = new UI.Row();
 	var materialMapEnabled = new UI.Checkbox( false ).onChange( update );
 	var materialMapEnabled = new UI.Checkbox( false ).onChange( update );
 	var materialMap = new UI.Texture().onChange( update );
 	var materialMap = new UI.Texture().onChange( update );
 
 
@@ -187,7 +199,7 @@ Sidebar.Material = function ( editor ) {
 
 
 	// alpha map
 	// alpha map
 
 
-	var materialAlphaMapRow = new UI.Panel();
+	var materialAlphaMapRow = new UI.Row();
 	var materialAlphaMapEnabled = new UI.Checkbox( false ).onChange( update );
 	var materialAlphaMapEnabled = new UI.Checkbox( false ).onChange( update );
 	var materialAlphaMap = new UI.Texture().onChange( update );
 	var materialAlphaMap = new UI.Texture().onChange( update );
 
 
@@ -199,7 +211,7 @@ Sidebar.Material = function ( editor ) {
 
 
 	// bump map
 	// bump map
 
 
-	var materialBumpMapRow = new UI.Panel();
+	var materialBumpMapRow = new UI.Row();
 	var materialBumpMapEnabled = new UI.Checkbox( false ).onChange( update );
 	var materialBumpMapEnabled = new UI.Checkbox( false ).onChange( update );
 	var materialBumpMap = new UI.Texture().onChange( update );
 	var materialBumpMap = new UI.Texture().onChange( update );
 	var materialBumpScale = new UI.Number( 1 ).setWidth( '30px' ).onChange( update );
 	var materialBumpScale = new UI.Number( 1 ).setWidth( '30px' ).onChange( update );
@@ -213,7 +225,7 @@ Sidebar.Material = function ( editor ) {
 
 
 	// normal map
 	// normal map
 
 
-	var materialNormalMapRow = new UI.Panel();
+	var materialNormalMapRow = new UI.Row();
 	var materialNormalMapEnabled = new UI.Checkbox( false ).onChange( update );
 	var materialNormalMapEnabled = new UI.Checkbox( false ).onChange( update );
 	var materialNormalMap = new UI.Texture().onChange( update );
 	var materialNormalMap = new UI.Texture().onChange( update );
 
 
@@ -225,7 +237,7 @@ Sidebar.Material = function ( editor ) {
 
 
 	// displacement map
 	// displacement map
 
 
-	var materialDisplacementMapRow = new UI.Panel();
+	var materialDisplacementMapRow = new UI.Row();
 	var materialDisplacementMapEnabled = new UI.Checkbox( false ).onChange( update );
 	var materialDisplacementMapEnabled = new UI.Checkbox( false ).onChange( update );
 	var materialDisplacementMap = new UI.Texture().onChange( update );
 	var materialDisplacementMap = new UI.Texture().onChange( update );
 	var materialDisplacementScale = new UI.Number( 1 ).setWidth( '30px' ).onChange( update );
 	var materialDisplacementScale = new UI.Number( 1 ).setWidth( '30px' ).onChange( update );
@@ -237,9 +249,33 @@ Sidebar.Material = function ( editor ) {
 
 
 	container.add( materialDisplacementMapRow );
 	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
 	// specular map
 
 
-	var materialSpecularMapRow = new UI.Panel();
+	var materialSpecularMapRow = new UI.Row();
 	var materialSpecularMapEnabled = new UI.Checkbox( false ).onChange( update );
 	var materialSpecularMapEnabled = new UI.Checkbox( false ).onChange( update );
 	var materialSpecularMap = new UI.Texture().onChange( update );
 	var materialSpecularMap = new UI.Texture().onChange( update );
 
 
@@ -251,7 +287,7 @@ Sidebar.Material = function ( editor ) {
 
 
 	// env map
 	// env map
 
 
-	var materialEnvMapRow = new UI.Panel();
+	var materialEnvMapRow = new UI.Row();
 	var materialEnvMapEnabled = new UI.Checkbox( false ).onChange( update );
 	var materialEnvMapEnabled = new UI.Checkbox( false ).onChange( update );
 	var materialEnvMap = new UI.Texture( THREE.SphericalReflectionMapping ).onChange( update );
 	var materialEnvMap = new UI.Texture( THREE.SphericalReflectionMapping ).onChange( update );
 	var materialReflectivity = new UI.Number( 1 ).setWidth( '30px' ).onChange( update );
 	var materialReflectivity = new UI.Number( 1 ).setWidth( '30px' ).onChange( update );
@@ -265,7 +301,7 @@ Sidebar.Material = function ( editor ) {
 
 
 	// light map
 	// light map
 
 
-	var materialLightMapRow = new UI.Panel();
+	var materialLightMapRow = new UI.Row();
 	var materialLightMapEnabled = new UI.Checkbox( false ).onChange( update );
 	var materialLightMapEnabled = new UI.Checkbox( false ).onChange( update );
 	var materialLightMap = new UI.Texture().onChange( update );
 	var materialLightMap = new UI.Texture().onChange( update );
 
 
@@ -277,7 +313,7 @@ Sidebar.Material = function ( editor ) {
 
 
 	// ambient occlusion map
 	// ambient occlusion map
 
 
-	var materialAOMapRow = new UI.Panel();
+	var materialAOMapRow = new UI.Row();
 	var materialAOMapEnabled = new UI.Checkbox( false ).onChange( update );
 	var materialAOMapEnabled = new UI.Checkbox( false ).onChange( update );
 	var materialAOMap = new UI.Texture().onChange( update );
 	var materialAOMap = new UI.Texture().onChange( update );
 	var materialAOScale = new UI.Number( 1 ).setRange( 0, 1 ).setWidth( '30px' ).onChange( update );
 	var materialAOScale = new UI.Number( 1 ).setRange( 0, 1 ).setWidth( '30px' ).onChange( update );
@@ -289,9 +325,21 @@ Sidebar.Material = function ( editor ) {
 
 
 	container.add( materialAOMapRow );
 	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
 	// side
 
 
-	var materialSideRow = new UI.Panel();
+	var materialSideRow = new UI.Row();
 	var materialSide = new UI.Select().setOptions( {
 	var materialSide = new UI.Select().setOptions( {
 
 
 		0: 'Front',
 		0: 'Front',
@@ -307,7 +355,7 @@ Sidebar.Material = function ( editor ) {
 
 
 	// shading
 	// shading
 
 
-	var materialShadingRow = new UI.Panel();
+	var materialShadingRow = new UI.Row();
 	var materialShading = new UI.Select().setOptions( {
 	var materialShading = new UI.Select().setOptions( {
 
 
 		0: 'No',
 		0: 'No',
@@ -323,7 +371,7 @@ Sidebar.Material = function ( editor ) {
 
 
 	// blending
 	// blending
 
 
-	var materialBlendingRow = new UI.Panel();
+	var materialBlendingRow = new UI.Row();
 	var materialBlending = new UI.Select().setOptions( {
 	var materialBlending = new UI.Select().setOptions( {
 
 
 		0: 'No',
 		0: 'No',
@@ -342,8 +390,8 @@ Sidebar.Material = function ( editor ) {
 
 
 	// opacity
 	// opacity
 
 
-	var materialOpacityRow = new UI.Panel();
-	var materialOpacity = new UI.Number().setWidth( '60px' ).setRange( 0, 1 ).onChange( update );
+	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( new UI.Text( 'Opacity' ).setWidth( '90px' ) );
 	materialOpacityRow.add( materialOpacity );
 	materialOpacityRow.add( materialOpacity );
@@ -352,7 +400,7 @@ Sidebar.Material = function ( editor ) {
 
 
 	// transparent
 	// transparent
 
 
-	var materialTransparentRow = new UI.Panel();
+	var materialTransparentRow = new UI.Row();
 	var materialTransparent = new UI.Checkbox().setLeft( '100px' ).onChange( update );
 	var materialTransparent = new UI.Checkbox().setLeft( '100px' ).onChange( update );
 
 
 	materialTransparentRow.add( new UI.Text( 'Transparent' ).setWidth( '90px' ) );
 	materialTransparentRow.add( new UI.Text( 'Transparent' ).setWidth( '90px' ) );
@@ -362,7 +410,7 @@ Sidebar.Material = function ( editor ) {
 
 
 	// alpha test
 	// alpha test
 
 
-	var materialAlphaTestRow = new UI.Panel();
+	var materialAlphaTestRow = new UI.Row();
 	var materialAlphaTest = new UI.Number().setWidth( '60px' ).setRange( 0, 1 ).onChange( update );
 	var materialAlphaTest = new UI.Number().setWidth( '60px' ).setRange( 0, 1 ).onChange( update );
 
 
 	materialAlphaTestRow.add( new UI.Text( 'Alpha Test' ).setWidth( '90px' ) );
 	materialAlphaTestRow.add( new UI.Text( 'Alpha Test' ).setWidth( '90px' ) );
@@ -372,7 +420,7 @@ Sidebar.Material = function ( editor ) {
 
 
 	// wireframe
 	// wireframe
 
 
-	var materialWireframeRow = new UI.Panel();
+	var materialWireframeRow = new UI.Row();
 	var materialWireframe = new UI.Checkbox( false ).onChange( update );
 	var materialWireframe = new UI.Checkbox( false ).onChange( update );
 	var materialWireframeLinewidth = new UI.Number( 1 ).setWidth( '60px' ).setRange( 0, 100 ).onChange( update );
 	var materialWireframeLinewidth = new UI.Number( 1 ).setWidth( '60px' ).setRange( 0, 100 ).onChange( update );
 
 
@@ -400,9 +448,9 @@ Sidebar.Material = function ( editor ) {
 
 
 		if ( material ) {
 		if ( material ) {
 
 
-			if ( material.uuid !== undefined ) {
+			if ( material.uuid !== undefined && material.uuid !== materialUUID.getValue() ) {
 
 
-				material.uuid = materialUUID.getValue();
+				editor.execute( new SetMaterialValueCommand( currentObject, 'uuid', materialUUID.getValue() ) );
 
 
 			}
 			}
 
 
@@ -410,7 +458,7 @@ Sidebar.Material = function ( editor ) {
 
 
 				material = new THREE[ materialClass.getValue() ]();
 				material = new THREE[ materialClass.getValue() ]();
 
 
-				object.material = material;
+				editor.execute( new SetMaterialCommand( currentObject, material ), 'New Material: ' + materialClass.getValue() );
 				// TODO Copy other references in the scene graph
 				// TODO Copy other references in the scene graph
 				// keeping name and UUID then.
 				// keeping name and UUID then.
 				// Also there should be means to create a unique
 				// Also there should be means to create a unique
@@ -419,27 +467,39 @@ Sidebar.Material = function ( editor ) {
 
 
 			}
 			}
 
 
-			if ( material.color !== undefined ) {
+			if ( material.color !== undefined && material.color.getHex() !== materialColor.getHexValue() ) {
+
+				editor.execute( new SetMaterialColorCommand( currentObject, 'color', materialColor.getHexValue() ) );
+
+			}
+
+			if ( material.roughness !== undefined && Math.abs( material.roughness - materialRoughness.getValue() ) >= 0.01 ) {
 
 
-				material.color.setHex( materialColor.getHexValue() );
+				editor.execute( new SetMaterialValueCommand( currentObject, 'roughness', materialRoughness.getValue() ) );
 
 
 			}
 			}
 
 
-			if ( material.emissive !== undefined ) {
+			if ( material.metalness !== undefined && Math.abs( material.metalness - materialMetalness.getValue() ) >= 0.01 ) {
 
 
-				material.emissive.setHex( materialEmissive.getHexValue() );
+				editor.execute( new SetMaterialValueCommand( currentObject, 'metalness', materialMetalness.getValue() ) );
 
 
 			}
 			}
 
 
-			if ( material.specular !== undefined ) {
+			if ( material.emissive !== undefined && material.emissive.getHex() !== materialEmissive.getHexValue() ) {
 
 
-				material.specular.setHex( materialSpecular.getHexValue() );
+				editor.execute( new SetMaterialColorCommand( currentObject, 'emissive', materialEmissive.getHexValue() ) );
 
 
 			}
 			}
 
 
-			if ( material.shininess !== undefined ) {
+			if ( material.specular !== undefined && material.specular.getHex() !== materialSpecular.getHexValue() ) {
 
 
-				material.shininess = materialShininess.getValue();
+				editor.execute( new SetMaterialColorCommand( currentObject, 'specular', materialSpecular.getHexValue() ) );
+
+			}
+
+			if ( material.shininess !== undefined && Math.abs( material.shininess - materialShininess.getValue() ) >= 0.01 ) {
+
+				editor.execute( new SetMaterialValueCommand( currentObject, 'shininess', materialShininess.getValue() ) );
 
 
 			}
 			}
 
 
@@ -449,16 +509,15 @@ Sidebar.Material = function ( editor ) {
 
 
 				if ( material.vertexColors !== vertexColors ) {
 				if ( material.vertexColors !== vertexColors ) {
 
 
-					material.vertexColors = vertexColors;
-					material.needsUpdate = true;
+					editor.execute( new SetMaterialValueCommand( currentObject, 'vertexColors', vertexColors ) );
 
 
 				}
 				}
 
 
 			}
 			}
 
 
-			if ( material.skinning !== undefined ) {
+			if ( material.skinning !== undefined && material.skinning !== materialSkinning.getValue() ) {
 
 
-				material.skinning = materialSkinning.getValue();
+				editor.execute( new SetMaterialValueCommand( currentObject, 'skinning', materialSkinning.getValue() ) );
 
 
 			}
 			}
 
 
@@ -468,8 +527,12 @@ Sidebar.Material = function ( editor ) {
 
 
 				if ( objectHasUvs ) {
 				if ( objectHasUvs ) {
 
 
-					material.map = mapEnabled ? materialMap.getValue() : null;
-					material.needsUpdate = true;
+					var map = mapEnabled ? materialMap.getValue() : null;
+					if ( material.map !== map ) {
+
+						editor.execute( new SetMaterialMapCommand( currentObject, 'map', map ) );
+
+					}
 
 
 				} else {
 				} else {
 
 
@@ -485,8 +548,12 @@ Sidebar.Material = function ( editor ) {
 
 
 				if ( objectHasUvs ) {
 				if ( objectHasUvs ) {
 
 
-					material.alphaMap = mapEnabled ? materialAlphaMap.getValue() : null;
-					material.needsUpdate = true;
+					var alphaMap = mapEnabled ? materialAlphaMap.getValue() : null;
+					if ( material.alphaMap !== alphaMap ) {
+
+						editor.execute( new SetMaterialMapCommand( currentObject, 'alphaMap', alphaMap ) );
+
+					}
 
 
 				} else {
 				} else {
 
 
@@ -502,9 +569,18 @@ Sidebar.Material = function ( editor ) {
 
 
 				if ( objectHasUvs ) {
 				if ( objectHasUvs ) {
 
 
-					material.bumpMap = bumpMapEnabled ? materialBumpMap.getValue() : null;
-					material.bumpScale = materialBumpScale.getValue();
-					material.needsUpdate = true;
+					var bumpMap = bumpMapEnabled ? materialBumpMap.getValue() : null;
+					if ( material.bumpMap !== bumpMap ) {
+
+						editor.execute( new SetMaterialMapCommand( currentObject, 'bumpMap', bumpMap ) );
+
+					}
+
+					if ( material.bumpScale !== materialBumpScale.getValue() ) {
+
+						editor.execute( new SetMaterialValueCommand( currentObject, 'bumpScale', materialBumpScale.getValue() ) );
+
+					}
 
 
 				} else {
 				} else {
 
 
@@ -520,8 +596,12 @@ Sidebar.Material = function ( editor ) {
 
 
 				if ( objectHasUvs ) {
 				if ( objectHasUvs ) {
 
 
-					material.normalMap = normalMapEnabled ? materialNormalMap.getValue() : null;
-					material.needsUpdate = true;
+					var normalMap = normalMapEnabled ? materialNormalMap.getValue() : null;
+					if ( material.normalMap !== normalMap ) {
+
+						editor.execute( new SetMaterialMapCommand( currentObject, 'normalMap', normalMap ) );
+
+					}
 
 
 				} else {
 				} else {
 
 
@@ -537,9 +617,18 @@ Sidebar.Material = function ( editor ) {
 
 
 				if ( objectHasUvs ) {
 				if ( objectHasUvs ) {
 
 
-					material.displacementMap = displacementMapEnabled ? materialDisplacementMap.getValue() : null;
-					material.displacementScale = materialDisplacementScale.getValue();
-					material.needsUpdate = true;
+					var displacementMap = displacementMapEnabled ? materialDisplacementMap.getValue() : null;
+					if ( material.displacementMap !== displacementMap ) {
+
+						editor.execute( new SetMaterialMapCommand( currentObject, 'displacementMap', displacementMap ) );
+
+					}
+
+					if ( material.displacementScale !== materialDisplacementScale.getValue() ) {
+
+						editor.execute( new SetMaterialValueCommand( currentObject, 'displacementScale', materialDisplacementScale.getValue() ) );
+
+					}
 
 
 				} else {
 				} else {
 
 
@@ -549,14 +638,72 @@ Sidebar.Material = function ( editor ) {
 
 
 			}
 			}
 
 
+			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 ) );
+
+					}
+
+					if ( material.displacementScale !== materialDisplacementScale.getValue() ) {
+
+						editor.execute( new SetMaterialValueCommand( currentObject, 'displacementScale', materialDisplacementScale.getValue() ) );
+
+					}
+
+				} 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 ) );
+
+					}
+
+					if ( material.displacementScale !== materialDisplacementScale.getValue() ) {
+
+						editor.execute( new SetMaterialValueCommand( currentObject, 'displacementScale', materialDisplacementScale.getValue() ) );
+
+					}
+
+				} else {
+
+					if ( metalnessMapEnabled ) textureWarning = true;
+
+				}
+
+			}
+
 			if ( material.specularMap !== undefined ) {
 			if ( material.specularMap !== undefined ) {
 
 
 				var specularMapEnabled = materialSpecularMapEnabled.getValue() === true;
 				var specularMapEnabled = materialSpecularMapEnabled.getValue() === true;
 
 
 				if ( objectHasUvs ) {
 				if ( objectHasUvs ) {
 
 
-					material.specularMap = specularMapEnabled ? materialSpecularMap.getValue() : null;
-					material.needsUpdate = true;
+					var specularMap = specularMapEnabled ? materialSpecularMap.getValue() : null;
+					if ( material.specularMap !== specularMap ) {
+
+						editor.execute( new SetMaterialMapCommand( currentObject, 'specularMap', specularMap ) );
+
+					}
 
 
 				} else {
 				} else {
 
 
@@ -570,12 +717,21 @@ Sidebar.Material = function ( editor ) {
 
 
 				var envMapEnabled = materialEnvMapEnabled.getValue() === true;
 				var envMapEnabled = materialEnvMapEnabled.getValue() === true;
 
 
-				material.envMap = envMapEnabled ? materialEnvMap.getValue() : null;
-				material.reflectivity = materialReflectivity.getValue();
-				material.needsUpdate = true;
+				var envMap = envMapEnabled ? materialEnvMap.getValue() : null;
 
 
-			}
+				if ( material.envMap !== envMap ) {
+
+					editor.execute( new SetMaterialMapCommand( currentObject, 'envMap', envMap ) );
+
+				}
+
+				if ( material.reflectivity !== materialReflectivity.getValue() ) {
 
 
+					editor.execute( new SetMaterialValueCommand( currentObject, 'reflectivity', materialReflectivity.getValue() ) );
+
+				}
+
+			}
 
 
 			if ( material.lightMap !== undefined ) {
 			if ( material.lightMap !== undefined ) {
 
 
@@ -583,8 +739,12 @@ Sidebar.Material = function ( editor ) {
 
 
 				if ( objectHasUvs ) {
 				if ( objectHasUvs ) {
 
 
-					material.lightMap = lightMapEnabled ? materialLightMap.getValue() : null;
-					material.needsUpdate = true;
+					var lightMap = lightMapEnabled ? materialLightMap.getValue() : null;
+					if ( material.lightMap !== lightMap ) {
+
+						editor.execute( new SetMaterialMapCommand( currentObject, 'lightMap', lightMap ) );
+
+					}
 
 
 				} else {
 				} else {
 
 
@@ -600,9 +760,18 @@ Sidebar.Material = function ( editor ) {
 
 
 				if ( objectHasUvs ) {
 				if ( objectHasUvs ) {
 
 
-					material.aoMap = aoMapEnabled ? materialAOMap.getValue() : null;
-					material.aoMapIntensity = materialAOScale.getValue();
-					material.needsUpdate = true;
+					var aoMap = aoMapEnabled ? materialAOMap.getValue() : null;
+					if ( material.aoMap !== aoMap ) {
+
+						editor.execute( new SetMaterialMapCommand( currentObject, 'aoMap', aoMap ) );
+
+					}
+
+					if ( material.aoMapIntensity !== materialAOScale.getValue() ) {
+
+						editor.execute( new SetMaterialValueCommand( currentObject, 'aoMapIntensity', materialAOScale.getValue() ) );
+
+					}
 
 
 				} else {
 				} else {
 
 
@@ -612,55 +781,92 @@ Sidebar.Material = function ( editor ) {
 
 
 			}
 			}
 
 
+			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 ) );
+
+					}
+
+				} else {
+
+					if ( emissiveMapEnabled ) textureWarning = true;
+
+				}
+
+			}
+
 			if ( material.side !== undefined ) {
 			if ( material.side !== undefined ) {
 
 
-				material.side = parseInt( materialSide.getValue() );
+				var side = parseInt( materialSide.getValue() );
+				if ( material.side !== side ) {
+
+					editor.execute( new SetMaterialValueCommand( currentObject, 'side', side ) );
+
+				}
+
 
 
 			}
 			}
 
 
 			if ( material.shading !== undefined ) {
 			if ( material.shading !== undefined ) {
 
 
-				material.shading = parseInt( materialShading.getValue() );
+				var shading = parseInt( materialShading.getValue() );
+				if ( material.shading !== shading ) {
+
+					editor.execute( new SetMaterialValueCommand( currentObject, 'shading', shading ) );
+
+				}
 
 
 			}
 			}
 
 
 			if ( material.blending !== undefined ) {
 			if ( material.blending !== undefined ) {
 
 
-				material.blending = parseInt( materialBlending.getValue() );
+				var blending = parseInt( materialBlending.getValue() );
+				if ( material.blending !== blending ) {
+
+					editor.execute( new SetMaterialValueCommand( currentObject, 'blending', blending ) );
+
+				}
 
 
 			}
 			}
 
 
-			if ( material.opacity !== undefined ) {
+			if ( material.opacity !== undefined && Math.abs( material.opacity - materialOpacity.getValue() ) >= 0.01 ) {
 
 
-				material.opacity = materialOpacity.getValue();
+				editor.execute( new SetMaterialValueCommand( currentObject, 'opacity', materialOpacity.getValue() ) );
 
 
 			}
 			}
 
 
-			if ( material.transparent !== undefined ) {
+			if ( material.transparent !== undefined && material.transparent !== materialTransparent.getValue() ) {
 
 
-				material.transparent = materialTransparent.getValue();
+				editor.execute( new SetMaterialValueCommand( currentObject, 'transparent', materialTransparent.getValue() ) );
 
 
 			}
 			}
 
 
-			if ( material.alphaTest !== undefined ) {
+			if ( material.alphaTest !== undefined && Math.abs( material.alphaTest - materialAlphaTest.getValue() ) >= 0.01 ) {
 
 
-				material.alphaTest = materialAlphaTest.getValue();
+				editor.execute( new SetMaterialValueCommand( currentObject, 'alphaTest', materialAlphaTest.getValue() ) );
 
 
 			}
 			}
 
 
-			if ( material.wireframe !== undefined ) {
+			if ( material.wireframe !== undefined && material.wireframe !== materialWireframe.getValue() ) {
 
 
-				material.wireframe = materialWireframe.getValue();
+				editor.execute( new SetMaterialValueCommand( currentObject, 'wireframe', materialWireframe.getValue() ) );
 
 
 			}
 			}
 
 
-			if ( material.wireframeLinewidth !== undefined ) {
+			if ( material.wireframeLinewidth !== undefined && Math.abs( material.wireframeLinewidth - materialWireframeLinewidth.getValue() ) >= 0.01 ) {
 
 
-				material.wireframeLinewidth = materialWireframeLinewidth.getValue();
+				editor.execute( new SetMaterialValueCommand( currentObject, 'wireframeLinewidth', materialWireframeLinewidth.getValue() ) );
 
 
 			}
 			}
 
 
-			refreshUi(false);
+			refreshUI( false );
 
 
 			signals.materialChanged.dispatch( material );
 			signals.materialChanged.dispatch( material );
 
 
@@ -672,7 +878,7 @@ Sidebar.Material = function ( editor ) {
 
 
 		}
 		}
 
 
-	};
+	}
 
 
 	//
 	//
 
 
@@ -681,6 +887,8 @@ Sidebar.Material = function ( editor ) {
 		var properties = {
 		var properties = {
 			'name': materialNameRow,
 			'name': materialNameRow,
 			'color': materialColorRow,
 			'color': materialColorRow,
+			'roughness': materialRoughnessRow,
+			'metalness': materialMetalnessRow,
 			'emissive': materialEmissiveRow,
 			'emissive': materialEmissiveRow,
 			'specular': materialSpecularRow,
 			'specular': materialSpecularRow,
 			'shininess': materialShininessRow,
 			'shininess': materialShininessRow,
@@ -692,10 +900,13 @@ Sidebar.Material = function ( editor ) {
 			'bumpMap': materialBumpMapRow,
 			'bumpMap': materialBumpMapRow,
 			'normalMap': materialNormalMapRow,
 			'normalMap': materialNormalMapRow,
 			'displacementMap': materialDisplacementMapRow,
 			'displacementMap': materialDisplacementMapRow,
+			'roughnessMap': materialRoughnessMapRow,
+			'metalnessMap': materialMetalnessMapRow,
 			'specularMap': materialSpecularMapRow,
 			'specularMap': materialSpecularMapRow,
 			'envMap': materialEnvMapRow,
 			'envMap': materialEnvMapRow,
 			'lightMap': materialLightMapRow,
 			'lightMap': materialLightMapRow,
 			'aoMap': materialAOMapRow,
 			'aoMap': materialAOMapRow,
+			'emissiveMap': materialEmissiveMapRow,
 			'side': materialSideRow,
 			'side': materialSideRow,
 			'shading': materialShadingRow,
 			'shading': materialShadingRow,
 			'blending': materialBlendingRow,
 			'blending': materialBlendingRow,
@@ -713,10 +924,12 @@ Sidebar.Material = function ( editor ) {
 
 
 		}
 		}
 
 
-	};
+	}
+
 
 
+	function refreshUI( resetTextureSelectors ) {
 
 
-	function refreshUi( resetTextureSelectors ) {
+		if ( ! currentObject ) return;
 
 
 		var material = currentObject.material;
 		var material = currentObject.material;
 
 
@@ -740,6 +953,18 @@ Sidebar.Material = function ( editor ) {
 
 
 		}
 		}
 
 
+		if ( material.roughness !== undefined ) {
+
+			materialRoughness.setValue( material.roughness );
+
+		}
+
+		if ( material.metalness !== undefined ) {
+
+			materialMetalness.setValue( material.metalness );
+
+		}
+
 		if ( material.emissive !== undefined ) {
 		if ( material.emissive !== undefined ) {
 
 
 			materialEmissive.setHexValue( material.emissive.getHexString() );
 			materialEmissive.setHexValue( material.emissive.getHexString() );
@@ -834,6 +1059,30 @@ Sidebar.Material = function ( editor ) {
 
 
 		}
 		}
 
 
+		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 ) {
 		if ( material.specularMap !== undefined ) {
 
 
 			materialSpecularMapEnabled.setValue( material.specularMap !== null );
 			materialSpecularMapEnabled.setValue( material.specularMap !== null );
@@ -886,6 +1135,18 @@ Sidebar.Material = function ( editor ) {
 
 
 		}
 		}
 
 
+		if ( material.emissiveMap !== undefined ) {
+
+			materialEmissiveMapEnabled.setValue( material.emissiveMap !== null );
+
+			if ( material.emissiveMap !== null || resetTextureSelectors ) {
+
+				materialEmissiveMap.setValue( material.emissiveMap );
+
+			}
+
+		}
+
 		if ( material.side !== undefined ) {
 		if ( material.side !== undefined ) {
 
 
 			materialSide.setValue( material.side );
 			materialSide.setValue( material.side );
@@ -947,7 +1208,7 @@ Sidebar.Material = function ( editor ) {
 			var objectChanged = object !== currentObject;
 			var objectChanged = object !== currentObject;
 
 
 			currentObject = object;
 			currentObject = object;
-			refreshUi(objectChanged);
+			refreshUI( objectChanged );
 			container.setDisplay( '' );
 			container.setDisplay( '' );
 
 
 		} else {
 		} else {
@@ -959,6 +1220,12 @@ Sidebar.Material = function ( editor ) {
 
 
 	} );
 	} );
 
 
+	signals.materialChanged.add( function () {
+
+		refreshUI();
+
+	} );
+
 	return container;
 	return container;
 
 
-}
+};

+ 126 - 95
editor/js/Sidebar.Object3D.js → editor/js/Sidebar.Object.js

@@ -2,25 +2,18 @@
  * @author mrdoob / http://mrdoob.com/
  * @author mrdoob / http://mrdoob.com/
  */
  */
 
 
-Sidebar.Object3D = function ( editor ) {
+Sidebar.Object = function ( editor ) {
 
 
 	var signals = editor.signals;
 	var signals = editor.signals;
 
 
-	var container = new UI.CollapsiblePanel();
-	container.setCollapsed( editor.config.getKey( 'ui/sidebar/object3d/collapsed' ) );
-	container.onCollapsedChange( function ( boolean ) {
-
-		editor.config.setKey( 'ui/sidebar/object3d/collapsed', boolean );
-
-	} );
+	var container = new UI.Panel();
+	container.setBorderTop( '0' );
+	container.setPaddingTop( '20px' );
 	container.setDisplay( 'none' );
 	container.setDisplay( 'none' );
 
 
-	var objectType = new UI.Text().setTextTransform( 'uppercase' );
-	container.addStatic( objectType );
-
 	// Actions
 	// Actions
 
 
-	var objectActions = new UI.Select().setPosition('absolute').setRight( '8px' ).setFontSize( '11px' );
+	var objectActions = new UI.Select().setPosition( 'absolute' ).setRight( '8px' ).setFontSize( '11px' );
 	objectActions.setOptions( {
 	objectActions.setOptions( {
 
 
 		'Actions': 'Actions',
 		'Actions': 'Actions',
@@ -41,37 +34,43 @@ Sidebar.Object3D = function ( editor ) {
 		switch ( this.getValue() ) {
 		switch ( this.getValue() ) {
 
 
 			case 'Reset Position':
 			case 'Reset Position':
-				object.position.set( 0, 0, 0 );
+				editor.execute( new SetPositionCommand( object, new THREE.Vector3( 0, 0, 0 ) ) );
 				break;
 				break;
 
 
 			case 'Reset Rotation':
 			case 'Reset Rotation':
-				object.rotation.set( 0, 0, 0 );
+				editor.execute( new SetRotationCommand( object, new THREE.Euler( 0, 0, 0 ) ) );
 				break;
 				break;
 
 
 			case 'Reset Scale':
 			case 'Reset Scale':
-				object.scale.set( 1, 1, 1 );
+				editor.execute( new SetScaleCommand( object, new THREE.Vector3( 1, 1, 1 ) ) );
 				break;
 				break;
 
 
 		}
 		}
 
 
 		this.setValue( 'Actions' );
 		this.setValue( 'Actions' );
 
 
-		signals.objectChanged.dispatch( object );
-
 	} );
 	} );
-	container.addStatic( objectActions );
+	// container.addStatic( objectActions );
+
+	// type
 
 
-	container.add( new UI.Break() );
+	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
 	// uuid
 
 
-	var objectUUIDRow = new UI.Panel();
+	var objectUUIDRow = new UI.Row();
 	var objectUUID = new UI.Input().setWidth( '115px' ).setFontSize( '12px' ).setDisabled( true );
 	var objectUUID = new UI.Input().setWidth( '115px' ).setFontSize( '12px' ).setDisabled( true );
 	var objectUUIDRenew = new UI.Button( '⟳' ).setMarginLeft( '7px' ).onClick( function () {
 	var objectUUIDRenew = new UI.Button( '⟳' ).setMarginLeft( '7px' ).onClick( function () {
 
 
 		objectUUID.setValue( THREE.Math.generateUUID() );
 		objectUUID.setValue( THREE.Math.generateUUID() );
 
 
-		editor.selected.uuid = objectUUID.getValue();
+		editor.execute( new SetUuidCommand( editor.selected, objectUUID.getValue() ) );
 
 
 	} );
 	} );
 
 
@@ -83,10 +82,10 @@ Sidebar.Object3D = function ( editor ) {
 
 
 	// name
 	// name
 
 
-	var objectNameRow = new UI.Panel();
+	var objectNameRow = new UI.Row();
 	var objectName = new UI.Input().setWidth( '150px' ).setFontSize( '12px' ).onChange( function () {
 	var objectName = new UI.Input().setWidth( '150px' ).setFontSize( '12px' ).onChange( function () {
 
 
-		editor.nameObject( editor.selected, objectName.getValue() );
+		editor.execute( new SetValueCommand( editor.selected, 'name', objectName.getValue() ) );
 
 
 	} );
 	} );
 
 
@@ -98,7 +97,7 @@ Sidebar.Object3D = function ( editor ) {
 	/*
 	/*
 	// parent
 	// parent
 
 
-	var objectParentRow = new UI.Panel();
+	var objectParentRow = new UI.Row();
 	var objectParent = new UI.Select().setWidth( '150px' ).setFontSize( '12px' ).onChange( update );
 	var objectParent = new UI.Select().setWidth( '150px' ).setFontSize( '12px' ).onChange( update );
 
 
 	objectParentRow.add( new UI.Text( 'Parent' ).setWidth( '90px' ) );
 	objectParentRow.add( new UI.Text( 'Parent' ).setWidth( '90px' ) );
@@ -109,7 +108,7 @@ Sidebar.Object3D = function ( editor ) {
 
 
 	// position
 	// position
 
 
-	var objectPositionRow = new UI.Panel();
+	var objectPositionRow = new UI.Row();
 	var objectPositionX = new UI.Number().setWidth( '50px' ).onChange( update );
 	var objectPositionX = new UI.Number().setWidth( '50px' ).onChange( update );
 	var objectPositionY = 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 );
 	var objectPositionZ = new UI.Number().setWidth( '50px' ).onChange( update );
@@ -121,7 +120,7 @@ Sidebar.Object3D = function ( editor ) {
 
 
 	// rotation
 	// rotation
 
 
-	var objectRotationRow = new UI.Panel();
+	var objectRotationRow = new UI.Row();
 	var objectRotationX = new UI.Number().setWidth( '50px' ).onChange( update );
 	var objectRotationX = new UI.Number().setWidth( '50px' ).onChange( update );
 	var objectRotationY = new UI.Number().setWidth( '50px' ).onChange( update );
 	var objectRotationY = new UI.Number().setWidth( '50px' ).onChange( update );
 	var objectRotationZ = new UI.Number().setWidth( '50px' ).onChange( update );
 	var objectRotationZ = new UI.Number().setWidth( '50px' ).onChange( update );
@@ -133,7 +132,7 @@ Sidebar.Object3D = function ( editor ) {
 
 
 	// scale
 	// scale
 
 
-	var objectScaleRow = new UI.Panel();
+	var objectScaleRow = new UI.Row();
 	var objectScaleLock = new UI.Checkbox( true ).setPosition( 'absolute' ).setLeft( '75px' );
 	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 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 objectScaleY = new UI.Number( 1 ).setRange( 0.01, Infinity ).setWidth( '50px' ).onChange( updateScaleY );
@@ -147,7 +146,7 @@ Sidebar.Object3D = function ( editor ) {
 
 
 	// fov
 	// fov
 
 
-	var objectFovRow = new UI.Panel();
+	var objectFovRow = new UI.Row();
 	var objectFov = new UI.Number().onChange( update );
 	var objectFov = new UI.Number().onChange( update );
 
 
 	objectFovRow.add( new UI.Text( 'Fov' ).setWidth( '90px' ) );
 	objectFovRow.add( new UI.Text( 'Fov' ).setWidth( '90px' ) );
@@ -157,7 +156,7 @@ Sidebar.Object3D = function ( editor ) {
 
 
 	// near
 	// near
 
 
-	var objectNearRow = new UI.Panel();
+	var objectNearRow = new UI.Row();
 	var objectNear = new UI.Number().onChange( update );
 	var objectNear = new UI.Number().onChange( update );
 
 
 	objectNearRow.add( new UI.Text( 'Near' ).setWidth( '90px' ) );
 	objectNearRow.add( new UI.Text( 'Near' ).setWidth( '90px' ) );
@@ -167,7 +166,7 @@ Sidebar.Object3D = function ( editor ) {
 
 
 	// far
 	// far
 
 
-	var objectFarRow = new UI.Panel();
+	var objectFarRow = new UI.Row();
 	var objectFar = new UI.Number().onChange( update );
 	var objectFar = new UI.Number().onChange( update );
 
 
 	objectFarRow.add( new UI.Text( 'Far' ).setWidth( '90px' ) );
 	objectFarRow.add( new UI.Text( 'Far' ).setWidth( '90px' ) );
@@ -177,7 +176,7 @@ Sidebar.Object3D = function ( editor ) {
 
 
 	// intensity
 	// intensity
 
 
-	var objectIntensityRow = new UI.Panel();
+	var objectIntensityRow = new UI.Row();
 	var objectIntensity = new UI.Number().setRange( 0, Infinity ).onChange( update );
 	var objectIntensity = new UI.Number().setRange( 0, Infinity ).onChange( update );
 
 
 	objectIntensityRow.add( new UI.Text( 'Intensity' ).setWidth( '90px' ) );
 	objectIntensityRow.add( new UI.Text( 'Intensity' ).setWidth( '90px' ) );
@@ -187,7 +186,7 @@ Sidebar.Object3D = function ( editor ) {
 
 
 	// color
 	// color
 
 
-	var objectColorRow = new UI.Panel();
+	var objectColorRow = new UI.Row();
 	var objectColor = new UI.Color().onChange( update );
 	var objectColor = new UI.Color().onChange( update );
 
 
 	objectColorRow.add( new UI.Text( 'Color' ).setWidth( '90px' ) );
 	objectColorRow.add( new UI.Text( 'Color' ).setWidth( '90px' ) );
@@ -197,7 +196,7 @@ Sidebar.Object3D = function ( editor ) {
 
 
 	// ground color
 	// ground color
 
 
-	var objectGroundColorRow = new UI.Panel();
+	var objectGroundColorRow = new UI.Row();
 	var objectGroundColor = new UI.Color().onChange( update );
 	var objectGroundColor = new UI.Color().onChange( update );
 
 
 	objectGroundColorRow.add( new UI.Text( 'Ground color' ).setWidth( '90px' ) );
 	objectGroundColorRow.add( new UI.Text( 'Ground color' ).setWidth( '90px' ) );
@@ -207,7 +206,7 @@ Sidebar.Object3D = function ( editor ) {
 
 
 	// distance
 	// distance
 
 
-	var objectDistanceRow = new UI.Panel();
+	var objectDistanceRow = new UI.Row();
 	var objectDistance = new UI.Number().setRange( 0, Infinity ).onChange( update );
 	var objectDistance = new UI.Number().setRange( 0, Infinity ).onChange( update );
 
 
 	objectDistanceRow.add( new UI.Text( 'Distance' ).setWidth( '90px' ) );
 	objectDistanceRow.add( new UI.Text( 'Distance' ).setWidth( '90px' ) );
@@ -217,7 +216,7 @@ Sidebar.Object3D = function ( editor ) {
 
 
 	// angle
 	// angle
 
 
-	var objectAngleRow = new UI.Panel();
+	var objectAngleRow = new UI.Row();
 	var objectAngle = new UI.Number().setPrecision( 3 ).setRange( 0, Math.PI / 2 ).onChange( update );
 	var objectAngle = new UI.Number().setPrecision( 3 ).setRange( 0, Math.PI / 2 ).onChange( update );
 
 
 	objectAngleRow.add( new UI.Text( 'Angle' ).setWidth( '90px' ) );
 	objectAngleRow.add( new UI.Text( 'Angle' ).setWidth( '90px' ) );
@@ -227,7 +226,7 @@ Sidebar.Object3D = function ( editor ) {
 
 
 	// exponent
 	// exponent
 
 
-	var objectExponentRow = new UI.Panel();
+	var objectExponentRow = new UI.Row();
 	var objectExponent = new UI.Number().setRange( 0, Infinity ).onChange( update );
 	var objectExponent = new UI.Number().setRange( 0, Infinity ).onChange( update );
 
 
 	objectExponentRow.add( new UI.Text( 'Exponent' ).setWidth( '90px' ) );
 	objectExponentRow.add( new UI.Text( 'Exponent' ).setWidth( '90px' ) );
@@ -237,7 +236,7 @@ Sidebar.Object3D = function ( editor ) {
 
 
 	// decay
 	// decay
 
 
-	var objectDecayRow = new UI.Panel();
+	var objectDecayRow = new UI.Row();
 	var objectDecay = new UI.Number().setRange( 0, Infinity ).onChange( update );
 	var objectDecay = new UI.Number().setRange( 0, Infinity ).onChange( update );
 
 
 	objectDecayRow.add( new UI.Text( 'Decay' ).setWidth( '90px' ) );
 	objectDecayRow.add( new UI.Text( 'Decay' ).setWidth( '90px' ) );
@@ -247,31 +246,24 @@ Sidebar.Object3D = function ( editor ) {
 
 
 	// shadow
 	// shadow
 
 
-	var objectShadowRow = new UI.Panel();
+	var objectShadowRow = new UI.Row();
 
 
 	objectShadowRow.add( new UI.Text( 'Shadow' ).setWidth( '90px' ) );
 	objectShadowRow.add( new UI.Text( 'Shadow' ).setWidth( '90px' ) );
 
 
-	var objectCastShadowSpan = new UI.Span().setMarginRight( '10px' );
-	var objectCastShadow = new UI.Checkbox().onChange( update );
-
-	objectCastShadowSpan.add( objectCastShadow );
-	objectCastShadowSpan.add( new UI.Text( 'cast' ).setMarginLeft( '3px' ) );
+	var objectCastShadow = new UI.THREE.Boolean( false, 'cast' ).onChange( update );
+	objectShadowRow.add( objectCastShadow );
 
 
-	objectShadowRow.add( objectCastShadowSpan );
+	var objectReceiveShadow = new UI.THREE.Boolean( false, 'receive' ).onChange( update );
+	objectShadowRow.add( objectReceiveShadow );
 
 
-	var objectReceiveShadowSpan = new UI.Span();
-	var objectReceiveShadow = new UI.Checkbox().onChange( update );
-
-	objectReceiveShadowSpan.add( objectReceiveShadow );
-	objectReceiveShadowSpan.add( new UI.Text( 'receive' ).setMarginLeft( '3px' ) );
-
-	objectShadowRow.add( objectReceiveShadowSpan );
+	var objectShadowRadius = new UI.Number( 1 ).onChange( update );
+	objectShadowRow.add( objectShadowRadius );
 
 
 	container.add( objectShadowRow );
 	container.add( objectShadowRow );
 
 
 	// visible
 	// visible
 
 
-	var objectVisibleRow = new UI.Panel();
+	var objectVisibleRow = new UI.Row();
 	var objectVisible = new UI.Checkbox().onChange( update );
 	var objectVisible = new UI.Checkbox().onChange( update );
 
 
 	objectVisibleRow.add( new UI.Text( 'Visible' ).setWidth( '90px' ) );
 	objectVisibleRow.add( new UI.Text( 'Visible' ).setWidth( '90px' ) );
@@ -283,7 +275,7 @@ Sidebar.Object3D = function ( editor ) {
 
 
 	var timeout;
 	var timeout;
 
 
-	var objectUserDataRow = new UI.Panel();
+	var objectUserDataRow = new UI.Row();
 	var objectUserData = new UI.TextArea().setWidth( '150px' ).setHeight( '40px' ).setFontSize( '12px' ).onChange( update );
 	var objectUserData = new UI.TextArea().setWidth( '150px' ).setHeight( '40px' ).setFontSize( '12px' ).onChange( update );
 	objectUserData.onKeyUp( function () {
 	objectUserData.onKeyUp( function () {
 
 
@@ -375,110 +367,136 @@ Sidebar.Object3D = function ( editor ) {
 
 
 				if ( object.parent.id !== newParentId && object.id !== newParentId ) {
 				if ( object.parent.id !== newParentId && object.id !== newParentId ) {
 
 
-					editor.moveObject( object, editor.scene.getObjectById( newParentId ) );
+					editor.execute( new MoveObjectCommand( object, editor.scene.getObjectById( newParentId ) ) );
 
 
 				}
 				}
 
 
 			}
 			}
 			*/
 			*/
 
 
-			object.position.x = objectPositionX.getValue();
-			object.position.y = objectPositionY.getValue();
-			object.position.z = objectPositionZ.getValue();
+			var newPosition = new THREE.Vector3( objectPositionX.getValue(), objectPositionY.getValue(), objectPositionZ.getValue() );
+			if ( object.position.distanceTo( newPosition ) >= 0.01 ) {
 
 
-			object.rotation.x = objectRotationX.getValue();
-			object.rotation.y = objectRotationY.getValue();
-			object.rotation.z = objectRotationZ.getValue();
+				editor.execute( new SetPositionCommand( object, newPosition ) );
 
 
-			object.scale.x = objectScaleX.getValue();
-			object.scale.y = objectScaleY.getValue();
-			object.scale.z = objectScaleZ.getValue();
+			}
 
 
-			if ( object.fov !== undefined ) {
+			var newRotation = new THREE.Euler( objectRotationX.getValue(), objectRotationY.getValue(), objectRotationZ.getValue() );
+			if ( object.rotation.toVector3().distanceTo( newRotation.toVector3() ) >= 0.01 ) {
 
 
-				object.fov = objectFov.getValue();
+				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();
 				object.updateProjectionMatrix();
 
 
 			}
 			}
 
 
-			if ( object.near !== undefined ) {
+			if ( object.near !== undefined && Math.abs( object.near - objectNear.getValue() ) >= 0.01 ) {
 
 
-				object.near = objectNear.getValue();
+				editor.execute( new SetValueCommand( object, 'near', objectNear.getValue() ) );
 
 
 			}
 			}
 
 
-			if ( object.far !== undefined ) {
+			if ( object.far !== undefined && Math.abs( object.far - objectFar.getValue() ) >= 0.01 ) {
 
 
-				object.far = objectFar.getValue();
+				editor.execute( new SetValueCommand( object, 'far', objectFar.getValue() ) );
 
 
 			}
 			}
 
 
-			if ( object.intensity !== undefined ) {
+			if ( object.intensity !== undefined && Math.abs( object.intensity - objectIntensity.getValue() ) >= 0.01 ) {
 
 
-				object.intensity = objectIntensity.getValue();
+				editor.execute( new SetValueCommand( object, 'intensity', objectIntensity.getValue() ) );
 
 
 			}
 			}
 
 
-			if ( object.color !== undefined ) {
+			if ( object.color !== undefined && object.color.getHex() !== objectColor.getHexValue() ) {
 
 
-				object.color.setHex( objectColor.getHexValue() );
+				editor.execute( new SetColorCommand( object, 'color', objectColor.getHexValue() ) );
 
 
 			}
 			}
 
 
-			if ( object.groundColor !== undefined ) {
+			if ( object.groundColor !== undefined && object.groundColor.getHex() !== objectGroundColor.getHexValue() ) {
 
 
-				object.groundColor.setHex( objectGroundColor.getHexValue() );
+				editor.execute( new SetColorCommand( object, 'groundColor', objectGroundColor.getHexValue() ) );
 
 
 			}
 			}
 
 
-			if ( object.distance !== undefined ) {
+			if ( object.distance !== undefined && Math.abs( object.distance - objectDistance.getValue() ) >= 0.01 ) {
 
 
-				object.distance = objectDistance.getValue();
+				editor.execute( new SetValueCommand( object, 'distance', objectDistance.getValue() ) );
 
 
 			}
 			}
 
 
-			if ( object.angle !== undefined ) {
+			if ( object.angle !== undefined && Math.abs( object.angle - objectAngle.getValue() ) >= 0.01 ) {
 
 
-				object.angle = objectAngle.getValue();
+				editor.execute( new SetValueCommand( object, 'angle', objectAngle.getValue() ) );
 
 
 			}
 			}
 
 
-			if ( object.exponent !== undefined ) {
+			if ( object.exponent !== undefined && Math.abs( object.exponent - objectExponent.getValue() ) >= 0.01 ) {
 
 
-				object.exponent = objectExponent.getValue();
+				editor.execute( new SetValueCommand( object, 'exponent', objectExponent.getValue() ) );
 
 
 			}
 			}
 
 
-			if ( object.decay !== undefined ) {
+			if ( object.decay !== undefined && Math.abs( object.decay - objectDecay.getValue() ) >= 0.01 ) {
 
 
-				object.decay = objectDecay.getValue();
+				editor.execute( new SetValueCommand( object, 'decay', objectDecay.getValue() ) );
 
 
 			}
 			}
 
 
-			if ( object.castShadow !== undefined ) {
+			if ( object.visible !== objectVisible.getValue() ) {
 
 
-				object.castShadow = objectCastShadow.getValue();
+				editor.execute( new SetValueCommand( object, 'visible', objectVisible.getValue() ) );
 
 
 			}
 			}
 
 
-			if ( object.receiveShadow !== undefined ) {
+			if ( object.castShadow !== objectCastShadow.getValue() ) {
+
+				editor.execute( new SetValueCommand( object, 'castShadow', objectCastShadow.getValue() ) );
+
+			}
 
 
-				var value = objectReceiveShadow.getValue();
+			if ( object.receiveShadow !== undefined ) {
 
 
-				if ( value !== object.receiveShadow ) {
+				if ( object.receiveShadow !== objectReceiveShadow.getValue() ) {
 
 
-					object.receiveShadow = value;
+					editor.execute( new SetValueCommand( object, 'receiveShadow', objectReceiveShadow.getValue() ) );
 					object.material.needsUpdate = true;
 					object.material.needsUpdate = true;
 
 
 				}
 				}
 
 
 			}
 			}
 
 
-			object.visible = objectVisible.getValue();
+			if ( object.shadow !== undefined ) {
+
+				if ( object.shadow.radius !== objectShadowRadius.getValue() ) {
+
+					editor.execute( new SetValueCommand( object.shadow, 'radius', objectShadowRadius.getValue() ) );
+
+				}
+
+			}
 
 
 			try {
 			try {
 
 
-				object.userData = JSON.parse( objectUserData.getValue() );
+				var userData = JSON.parse( objectUserData.getValue() );
+				if ( JSON.stringify( object.userData ) != JSON.stringify( userData ) ) {
+
+					editor.execute( new SetValueCommand( object, 'userData', userData ) );
+
+				}
 
 
 			} catch ( exception ) {
 			} catch ( exception ) {
 
 
@@ -486,8 +504,6 @@ Sidebar.Object3D = function ( editor ) {
 
 
 			}
 			}
 
 
-			signals.objectChanged.dispatch( object );
-
 		}
 		}
 
 
 	}
 	}
@@ -507,7 +523,8 @@ Sidebar.Object3D = function ( editor ) {
 			'exponent' : objectExponentRow,
 			'exponent' : objectExponentRow,
 			'decay' : objectDecayRow,
 			'decay' : objectDecayRow,
 			'castShadow' : objectShadowRow,
 			'castShadow' : objectShadowRow,
-			'receiveShadow' : objectReceiveShadowSpan
+			'receiveShadow' : objectReceiveShadow,
+			'shadow': objectShadowRadius
 		};
 		};
 
 
 		for ( var property in properties ) {
 		for ( var property in properties ) {
@@ -579,6 +596,14 @@ Sidebar.Object3D = function ( editor ) {
 
 
 	} );
 	} );
 
 
+	signals.refreshSidebarObject3D.add( function ( object ) {
+
+		if ( object !== editor.selected ) return;
+
+		updateUI( object );
+
+	} );
+
 	function updateUI( object ) {
 	function updateUI( object ) {
 
 
 		objectType.setValue( object.type );
 		objectType.setValue( object.type );
@@ -678,6 +703,12 @@ Sidebar.Object3D = function ( editor ) {
 
 
 		}
 		}
 
 
+		if ( object.shadow !== undefined ) {
+
+			objectShadowRadius.setValue( object.shadow.radius );
+
+		}
+
 		objectVisible.setValue( object.visible );
 		objectVisible.setValue( object.visible );
 
 
 		try {
 		try {
@@ -699,4 +730,4 @@ Sidebar.Object3D = function ( editor ) {
 
 
 	return container;
 	return container;
 
 
-}
+};

+ 22 - 39
editor/js/Sidebar.Project.js

@@ -17,16 +17,9 @@ Sidebar.Project = function ( editor ) {
 
 
 	};
 	};
 
 
-	var container = new UI.CollapsiblePanel();
-	container.setCollapsed( config.getKey( 'ui/sidebar/project/collapsed' ) );
-	container.onCollapsedChange( function ( boolean ) {
-
-		config.setKey( 'ui/sidebar/project/collapsed', boolean );
-
-	} );
-
-	container.addStatic( new UI.Text( 'PROJECT' ) );
-	container.add( new UI.Break() );
+	var container = new UI.Panel();
+	container.setBorderTop( '0' );
+	container.setPaddingTop( '20px' );
 
 
 	// class
 	// class
 
 
@@ -40,22 +33,13 @@ Sidebar.Project = function ( editor ) {
 
 
 	}
 	}
 
 
-	var rendererTypeRow = new UI.Panel();
+	var rendererTypeRow = new UI.Row();
 	var rendererType = new UI.Select().setOptions( options ).setWidth( '150px' ).onChange( function () {
 	var rendererType = new UI.Select().setOptions( options ).setWidth( '150px' ).onChange( function () {
 
 
 		var value = this.getValue();
 		var value = this.getValue();
 
 
-		if ( value === 'WebGLRenderer' ) {
-
-			rendererPropertiesRow.setDisplay( '' );
-
-		} else {
-
-			rendererPropertiesRow.setDisplay( 'none' );
-
-		}
-
 		config.setKey( 'project/renderer', value );
 		config.setKey( 'project/renderer', value );
+
 		updateRenderer();
 		updateRenderer();
 
 
 	} );
 	} );
@@ -73,42 +57,32 @@ Sidebar.Project = function ( editor ) {
 
 
 	// antialiasing
 	// antialiasing
 
 
-	var rendererPropertiesRow = new UI.Panel();
+	var rendererPropertiesRow = new UI.Row();
 	rendererPropertiesRow.add( new UI.Text( '' ).setWidth( '90px' ) );
 	rendererPropertiesRow.add( new UI.Text( '' ).setWidth( '90px' ) );
 
 
-	var rendererAntialiasSpan = new UI.Span().setMarginRight( '10px' );
-	var rendererAntialias = new UI.Checkbox( config.getKey( 'project/renderer/antialias' ) ).setLeft( '100px' ).onChange( function () {
+	var rendererAntialias = new UI.THREE.Boolean( config.getKey( 'project/renderer/antialias' ), 'antialias' ).onChange( function () {
 
 
 		config.setKey( 'project/renderer/antialias', this.getValue() );
 		config.setKey( 'project/renderer/antialias', this.getValue() );
 		updateRenderer();
 		updateRenderer();
 
 
 	} );
 	} );
-
-	rendererAntialiasSpan.add( rendererAntialias );
-	rendererAntialiasSpan.add( new UI.Text( 'antialias' ).setMarginLeft( '3px' ) );
-
-	rendererPropertiesRow.add( rendererAntialiasSpan );
+	rendererPropertiesRow.add( rendererAntialias );
 
 
 	// shadow
 	// shadow
 
 
-	var rendererShadowsSpan = new UI.Span();
-	var rendererShadows = new UI.Checkbox( config.getKey( 'project/renderer/shadows' ) ).setLeft( '100px' ).onChange( function () {
+	var rendererShadows = new UI.THREE.Boolean( config.getKey( 'project/renderer/shadows' ), 'shadows' ).onChange( function () {
 
 
 		config.setKey( 'project/renderer/shadows', this.getValue() );
 		config.setKey( 'project/renderer/shadows', this.getValue() );
 		updateRenderer();
 		updateRenderer();
 
 
 	} );
 	} );
-
-	rendererShadowsSpan.add( rendererShadows );
-	rendererShadowsSpan.add( new UI.Text( 'shadows' ).setMarginLeft( '3px' ) );
-
-	rendererPropertiesRow.add( rendererShadowsSpan );
+	rendererPropertiesRow.add( rendererShadows );
 
 
 	container.add( rendererPropertiesRow );
 	container.add( rendererPropertiesRow );
 
 
 	// VR
 	// VR
 
 
-	var vrRow = new UI.Panel();
+	var vrRow = new UI.Row();
 	var vr = new UI.Checkbox( config.getKey( 'project/vr' ) ).setLeft( '100px' ).onChange( function () {
 	var vr = new UI.Checkbox( config.getKey( 'project/vr' ) ).setLeft( '100px' ).onChange( function () {
 
 
 		config.setKey( 'project/vr', this.getValue() );
 		config.setKey( 'project/vr', this.getValue() );
@@ -137,8 +111,17 @@ Sidebar.Project = function ( editor ) {
 
 
 		}
 		}
 
 
+		rendererPropertiesRow.setDisplay( type === 'WebGLRenderer' ? '' : 'none' );
+
 		var renderer = new rendererTypes[ type ]( { antialias: antialias } );
 		var renderer = new rendererTypes[ type ]( { antialias: antialias } );
-		if ( shadows && renderer.shadowMap ) renderer.shadowMap.enabled = true;
+
+		if ( shadows && renderer.shadowMap ) {
+
+			renderer.shadowMap.enabled = true;
+			// renderer.shadowMap.type = THREE.PCFSoftShadowMap;
+
+		}
+
 		signals.rendererChanged.dispatch( renderer );
 		signals.rendererChanged.dispatch( renderer );
 
 
 	}
 	}
@@ -147,4 +130,4 @@ Sidebar.Project = function ( editor ) {
 
 
 	return container;
 	return container;
 
 
-}
+};

+ 76 - 0
editor/js/Sidebar.Properties.js

@@ -0,0 +1,76 @@
+/**
+ * @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;
+
+};

+ 25 - 17
editor/js/Sidebar.Scene.js

@@ -6,20 +6,14 @@ Sidebar.Scene = function ( editor ) {
 
 
 	var signals = editor.signals;
 	var signals = editor.signals;
 
 
-	var container = new UI.CollapsiblePanel();
-	container.setCollapsed( editor.config.getKey( 'ui/sidebar/scene/collapsed' ) );
-	container.onCollapsedChange( function ( boolean ) {
-
-		editor.config.setKey( 'ui/sidebar/scene/collapsed', boolean );
-
-	} );
-
-	container.addStatic( new UI.Text( 'SCENE' ) );
-	container.add( new UI.Break() );
+	var container = new UI.Panel();
+	container.setBorderTop( '0' );
+	container.setPaddingTop( '20px' );
 
 
 	var ignoreObjectSelectedSignal = false;
 	var ignoreObjectSelectedSignal = false;
 
 
 	var outliner = new UI.Outliner( editor );
 	var outliner = new UI.Outliner( editor );
+	outliner.setId( 'outliner' );
 	outliner.onChange( function () {
 	outliner.onChange( function () {
 
 
 		ignoreObjectSelectedSignal = true;
 		ignoreObjectSelectedSignal = true;
@@ -49,7 +43,7 @@ Sidebar.Scene = function ( editor ) {
 
 
 	};
 	};
 
 
-	var fogTypeRow = new UI.Panel();
+	var fogTypeRow = new UI.Row();
 	var fogType = new UI.Select().setOptions( {
 	var fogType = new UI.Select().setOptions( {
 
 
 		'None': 'None',
 		'None': 'None',
@@ -73,7 +67,7 @@ Sidebar.Scene = function ( editor ) {
 
 
 	// fog color
 	// fog color
 
 
-	var fogColorRow = new UI.Panel();
+	var fogColorRow = new UI.Row();
 	fogColorRow.setDisplay( 'none' );
 	fogColorRow.setDisplay( 'none' );
 
 
 	var fogColor = new UI.Color().setValue( '#aaaaaa' )
 	var fogColor = new UI.Color().setValue( '#aaaaaa' )
@@ -90,7 +84,7 @@ Sidebar.Scene = function ( editor ) {
 
 
 	// fog near
 	// fog near
 
 
-	var fogNearRow = new UI.Panel();
+	var fogNearRow = new UI.Row();
 	fogNearRow.setDisplay( 'none' );
 	fogNearRow.setDisplay( 'none' );
 
 
 	var fogNear = new UI.Number( 1 ).setWidth( '60px' ).setRange( 0, Infinity ).onChange( updateFogParameters );
 	var fogNear = new UI.Number( 1 ).setWidth( '60px' ).setRange( 0, Infinity ).onChange( updateFogParameters );
@@ -100,7 +94,7 @@ Sidebar.Scene = function ( editor ) {
 
 
 	container.add( fogNearRow );
 	container.add( fogNearRow );
 
 
-	var fogFarRow = new UI.Panel();
+	var fogFarRow = new UI.Row();
 	fogFarRow.setDisplay( 'none' );
 	fogFarRow.setDisplay( 'none' );
 
 
 	// fog far
 	// fog far
@@ -114,7 +108,7 @@ Sidebar.Scene = function ( editor ) {
 
 
 	// fog density
 	// fog density
 
 
-	var fogDensityRow = new UI.Panel();
+	var fogDensityRow = new UI.Row();
 	fogDensityRow.setDisplay( 'none' );
 	fogDensityRow.setDisplay( 'none' );
 
 
 	var fogDensity = new UI.Number( 0.00025 ).setWidth( '60px' ).setRange( 0, 0.1 ).setPrecision( 5 ).onChange( updateFogParameters );
 	var fogDensity = new UI.Number( 0.00025 ).setWidth( '60px' ).setRange( 0, 0.1 ).setPrecision( 5 ).onChange( updateFogParameters );
@@ -133,8 +127,20 @@ Sidebar.Scene = function ( editor ) {
 
 
 		var options = [];
 		var options = [];
 
 
-		// options.push( { value: camera.id, html: '<span class="type ' + camera.type + '"></span> ' + camera.name } );
-		options.push( { static: true, value: scene.id, html: '<span class="type ' + scene.type + '"></span> ' + scene.name } );
+		options.push( { static: true, value: camera.id, html: '<span class="type ' + camera.type + '"></span> ' + camera.name } );
+		options.push( { static: true, value: scene.id, html: '<span class="type ' + scene.type + '"></span> ' + scene.name + getScript( scene.uuid ) } );
+
+		function getScript( uuid ) {
+
+			if ( editor.scripts[ uuid ] !== undefined ) {
+
+				return ' <span class="type Script"></span>';
+
+			}
+
+			return '';
+
+		}
 
 
 		( function addObjects( objects, pad ) {
 		( function addObjects( objects, pad ) {
 
 
@@ -154,6 +160,8 @@ Sidebar.Scene = function ( editor ) {
 
 
 				}
 				}
 
 
+				html += getScript( object.uuid );
+
 				options.push( { value: object.id, html: html } );
 				options.push( { value: object.id, html: html } );
 
 
 				addObjects( object.children, pad + '&nbsp;&nbsp;&nbsp;' );
 				addObjects( object.children, pad + '&nbsp;&nbsp;&nbsp;' );

+ 5 - 6
editor/js/Sidebar.Script.js

@@ -20,14 +20,14 @@ Sidebar.Script = function ( editor ) {
 
 
 	//
 	//
 
 
-	var scriptsContainer = new UI.Panel();
+	var scriptsContainer = new UI.Row();
 	container.add( scriptsContainer );
 	container.add( scriptsContainer );
 
 
 	var newScript = new UI.Button( 'New' );
 	var newScript = new UI.Button( 'New' );
 	newScript.onClick( function () {
 	newScript.onClick( function () {
 
 
 		var script = { name: '', source: 'function update( event ) {}' };
 		var script = { name: '', source: 'function update( event ) {}' };
-		editor.addScript( editor.selected, script );
+		editor.execute( new AddScriptCommand( editor.selected, script ) );
 
 
 	} );
 	} );
 	container.add( newScript );
 	container.add( newScript );
@@ -63,9 +63,7 @@ Sidebar.Script = function ( editor ) {
 					var name = new UI.Input( script.name ).setWidth( '130px' ).setFontSize( '12px' );
 					var name = new UI.Input( script.name ).setWidth( '130px' ).setFontSize( '12px' );
 					name.onChange( function () {
 					name.onChange( function () {
 
 
-						script.name = this.getValue();
-
-						signals.scriptChanged.dispatch();
+						editor.execute( new SetScriptValueCommand( editor.selected, script, 'name', this.getValue() ) );
 
 
 					} );
 					} );
 					scriptsContainer.add( name );
 					scriptsContainer.add( name );
@@ -85,7 +83,7 @@ Sidebar.Script = function ( editor ) {
 
 
 						if ( confirm( 'Are you sure?' ) ) {
 						if ( confirm( 'Are you sure?' ) ) {
 
 
-							editor.removeScript( editor.selected, script );
+							editor.execute( new RemoveScriptCommand( editor.selected, script ) );
 
 
 						}
 						}
 
 
@@ -122,6 +120,7 @@ Sidebar.Script = function ( editor ) {
 
 
 	signals.scriptAdded.add( update );
 	signals.scriptAdded.add( update );
 	signals.scriptRemoved.add( update );
 	signals.scriptRemoved.add( update );
+	signals.scriptChanged.add( update );
 
 
 	return container;
 	return container;
 
 

+ 47 - 0
editor/js/Sidebar.Settings.js

@@ -0,0 +1,47 @@
+/**
+ * @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;
+
+};

+ 68 - 7
editor/js/Sidebar.js

@@ -7,13 +7,74 @@ var Sidebar = function ( editor ) {
 	var container = new UI.Panel();
 	var container = new UI.Panel();
 	container.setId( 'sidebar' );
 	container.setId( 'sidebar' );
 
 
-	container.add( new Sidebar.Project( editor ) );
-	container.add( new Sidebar.Scene( editor ) );
-	container.add( new Sidebar.Object3D( editor ) );
-	container.add( new Sidebar.Geometry( editor ) );
-	container.add( new Sidebar.Material( editor ) );
-	container.add( new Sidebar.Animation( editor ) );
-	container.add( new Sidebar.Script( editor ) );
+	//
+
+	var sceneTab = new UI.Text( 'SCENE' ).onClick( onClick );
+	var projectTab = new UI.Text( 'PROJECT' ).onClick( onClick );
+	var settingsTab = new UI.Text( 'SETTINGS' ).onClick( onClick );
+
+	var tabs = new UI.Div();
+	tabs.setId( 'tabs' );
+	tabs.add( sceneTab, projectTab, settingsTab );
+	container.add( tabs );
+
+	function onClick( event ) {
+
+		select( event.target.textContent );
+
+	}
+
+	//
+
+	var scene = new UI.Span().add(
+		new Sidebar.Scene( editor ),
+		new Sidebar.Properties( editor ),
+		new Sidebar.Animation( editor ),
+		new Sidebar.Script( editor )
+	);
+	container.add( scene );
+
+	var project = new UI.Span().add(
+		new Sidebar.Project( editor )
+	);
+	container.add( project );
+
+	var settings = new UI.Span().add(
+		new Sidebar.Settings( editor ),
+		new Sidebar.History( editor )
+	);
+	container.add( settings );
+
+	//
+
+	function select( section ) {
+
+		sceneTab.setClass( '' );
+		projectTab.setClass( '' );
+		settingsTab.setClass( '' );
+
+		scene.setDisplay( 'none' );
+		project.setDisplay( 'none' );
+		settings.setDisplay( 'none' );
+
+		switch ( section ) {
+			case 'SCENE':
+				sceneTab.setClass( 'selected' );
+				scene.setDisplay( '' );
+				break;
+			case 'PROJECT':
+				projectTab.setClass( 'selected' );
+				project.setDisplay( '' );
+				break;
+			case 'SETTINGS':
+				settingsTab.setClass( 'selected' );
+				settings.setDisplay( '' );
+				break;
+		}
+
+	}
+
+	select( 'SCENE' );
 
 
 	return container;
 	return container;
 
 

+ 4 - 7
editor/js/Toolbar.js

@@ -38,20 +38,17 @@ var Toolbar = function ( editor ) {
 	// grid
 	// grid
 
 
 	var grid = new UI.Number( 25 ).setWidth( '40px' ).onChange( update );
 	var grid = new UI.Number( 25 ).setWidth( '40px' ).onChange( update );
-	buttons.add( new UI.Text( 'Grid: ' ) );
+	buttons.add( new UI.Text( 'grid: ' ) );
 	buttons.add( grid );
 	buttons.add( grid );
 
 
-	var snap = new UI.Checkbox( false ).onChange( update ).setMarginLeft( '10px' );
+	var snap = new UI.THREE.Boolean( false, 'snap' ).onChange( update );
 	buttons.add( snap );
 	buttons.add( snap );
-	buttons.add( new UI.Text( 'snap' ).setMarginLeft( '3px' ) );
 
 
-	var local = new UI.Checkbox( false ).onChange( update ).setMarginLeft( '10px' );
+	var local = new UI.THREE.Boolean( false, 'local' ).onChange( update );
 	buttons.add( local );
 	buttons.add( local );
-	buttons.add( new UI.Text( 'local' ).setMarginLeft( '3px' ) );
 
 
-	var showGrid = new UI.Checkbox().onChange( update ).setValue( true ).setMarginLeft( '10px' );
+	var showGrid = new UI.THREE.Boolean( true, 'show' ).onChange( update );
 	buttons.add( showGrid );
 	buttons.add( showGrid );
-	buttons.add( new UI.Text( 'show' ).setMarginLeft( '3px' ) );
 
 
 	function update() {
 	function update() {
 
 

+ 67 - 68
editor/js/Viewport.js

@@ -19,7 +19,7 @@ var Viewport = function ( editor ) {
 
 
 	// helpers
 	// helpers
 
 
-	var grid = new THREE.GridHelper( 500, 25 );
+	var grid = new THREE.GridHelper( 30, 1 );
 	sceneHelpers.add( grid );
 	sceneHelpers.add( grid );
 
 
 	//
 	//
@@ -34,7 +34,9 @@ var Viewport = function ( editor ) {
 	selectionBox.visible = false;
 	selectionBox.visible = false;
 	sceneHelpers.add( selectionBox );
 	sceneHelpers.add( selectionBox );
 
 
-	var matrix = new THREE.Matrix4();
+	var objectPositionOnDown = null;
+	var objectRotationOnDown = null;
+	var objectScaleOnDown = null;
 
 
 	var transformControls = new THREE.TransformControls( camera, container.dom );
 	var transformControls = new THREE.TransformControls( camera, container.dom );
 	transformControls.addEventListener( 'change', function () {
 	transformControls.addEventListener( 'change', function () {
@@ -51,6 +53,8 @@ var Viewport = function ( editor ) {
 
 
 			}
 			}
 
 
+			signals.refreshSidebarObject3D.dispatch( object );
+
 		}
 		}
 
 
 		render();
 		render();
@@ -60,7 +64,9 @@ var Viewport = function ( editor ) {
 
 
 		var object = transformControls.object;
 		var object = transformControls.object;
 
 
-		matrix.copy( object.matrix );
+		objectPositionOnDown = object.position.clone();
+		objectRotationOnDown = object.rotation.clone();
+		objectScaleOnDown = object.scale.clone();
 
 
 		controls.enabled = false;
 		controls.enabled = false;
 
 
@@ -69,26 +75,44 @@ var Viewport = function ( editor ) {
 
 
 		var object = transformControls.object;
 		var object = transformControls.object;
 
 
-		if ( matrix.equals( object.matrix ) === false ) {
+		if ( object !== null ) {
+
+			switch ( transformControls.getMode() ) {
+
+				case 'translate':
+
+					if ( ! objectPositionOnDown.equals( object.position ) ) {
+
+						editor.execute( new SetPositionCommand( object, object.position, objectPositionOnDown ) );
+
+					}
+
+					break;
+
+				case 'rotate':
+
+					if ( ! objectRotationOnDown.equals( object.rotation ) ) {
+
+						editor.execute( new SetRotationCommand( object, object.rotation, objectRotationOnDown ) );
+
+					}
+
+					break;
+
+				case 'scale':
 
 
-			( function ( matrix1, matrix2 ) {
+					if ( ! objectScaleOnDown.equals( object.scale ) ) {
+
+						editor.execute( new SetScaleCommand( object, object.scale, objectScaleOnDown ) );
 
 
-				editor.history.add(
-					function () {
-						matrix1.decompose( object.position, object.quaternion, object.scale );
-						signals.objectChanged.dispatch( object );
-					},
-					function () {
-						matrix2.decompose( object.position, object.quaternion, object.scale );
-						signals.objectChanged.dispatch( object );
 					}
 					}
-				);
 
 
-			} )( matrix.clone(), object.matrix.clone() );
+					break;
+
+			}
 
 
 		}
 		}
 
 
-		signals.objectChanged.dispatch( object );
 		controls.enabled = true;
 		controls.enabled = true;
 
 
 	} );
 	} );
@@ -118,7 +142,7 @@ var Viewport = function ( editor ) {
 
 
 		return raycaster.intersectObjects( objects );
 		return raycaster.intersectObjects( objects );
 
 
-	};
+	}
 
 
 	var onDownPosition = new THREE.Vector2();
 	var onDownPosition = new THREE.Vector2();
 	var onUpPosition = new THREE.Vector2();
 	var onUpPosition = new THREE.Vector2();
@@ -129,11 +153,11 @@ var Viewport = function ( editor ) {
 		var rect = dom.getBoundingClientRect();
 		var rect = dom.getBoundingClientRect();
 		return [ ( x - rect.left ) / rect.width, ( y - rect.top ) / rect.height ];
 		return [ ( x - rect.left ) / rect.width, ( y - rect.top ) / rect.height ];
 
 
-	};
+	}
 
 
 	function handleClick() {
 	function handleClick() {
 
 
-		if ( onDownPosition.distanceTo( onUpPosition ) == 0 ) {
+		if ( onDownPosition.distanceTo( onUpPosition ) === 0 ) {
 
 
 			var intersects = getIntersects( onUpPosition, objects );
 			var intersects = getIntersects( onUpPosition, objects );
 
 
@@ -163,7 +187,7 @@ var Viewport = function ( editor ) {
 
 
 		}
 		}
 
 
-	};
+	}
 
 
 	function onMouseDown( event ) {
 	function onMouseDown( event ) {
 
 
@@ -174,7 +198,7 @@ var Viewport = function ( editor ) {
 
 
 		document.addEventListener( 'mouseup', onMouseUp, false );
 		document.addEventListener( 'mouseup', onMouseUp, false );
 
 
-	};
+	}
 
 
 	function onMouseUp( event ) {
 	function onMouseUp( event ) {
 
 
@@ -185,7 +209,7 @@ var Viewport = function ( editor ) {
 
 
 		document.removeEventListener( 'mouseup', onMouseUp, false );
 		document.removeEventListener( 'mouseup', onMouseUp, false );
 
 
-	};
+	}
 
 
 	function onTouchStart( event ) {
 	function onTouchStart( event ) {
 
 
@@ -196,7 +220,7 @@ var Viewport = function ( editor ) {
 
 
 		document.addEventListener( 'touchend', onTouchEnd, false );
 		document.addEventListener( 'touchend', onTouchEnd, false );
 
 
-	};
+	}
 
 
 	function onTouchEnd( event ) {
 	function onTouchEnd( event ) {
 
 
@@ -209,7 +233,7 @@ var Viewport = function ( editor ) {
 
 
 		document.removeEventListener( 'touchend', onTouchEnd, false );
 		document.removeEventListener( 'touchend', onTouchEnd, false );
 
 
-	};
+	}
 
 
 	function onDoubleClick( event ) {
 	function onDoubleClick( event ) {
 
 
@@ -226,7 +250,7 @@ var Viewport = function ( editor ) {
 
 
 		}
 		}
 
 
-	};
+	}
 
 
 	container.dom.addEventListener( 'mousedown', onMouseDown, false );
 	container.dom.addEventListener( 'mousedown', onMouseDown, false );
 	container.dom.addEventListener( 'touchstart', onTouchStart, false );
 	container.dom.addEventListener( 'touchstart', onTouchStart, false );
@@ -358,9 +382,13 @@ var Viewport = function ( editor ) {
 
 
 	} );
 	} );
 
 
-	signals.geometryChanged.add( function ( geometry ) {
+	signals.geometryChanged.add( function ( object ) {
+
+		if ( object !== null ) {
+
+			selectionBox.update( object );
 
 
-		selectionBox.update( editor.selected );
+		}
 
 
 		render();
 		render();
 
 
@@ -368,24 +396,22 @@ var Viewport = function ( editor ) {
 
 
 	signals.objectAdded.add( function ( object ) {
 	signals.objectAdded.add( function ( object ) {
 
 
-		var materialsNeedUpdate = false;
-
 		object.traverse( function ( child ) {
 		object.traverse( function ( child ) {
 
 
-			if ( child instanceof THREE.Light ) materialsNeedUpdate = true;
-
 			objects.push( child );
 			objects.push( child );
 
 
 		} );
 		} );
 
 
-		if ( materialsNeedUpdate === true ) updateMaterials();
-
 	} );
 	} );
 
 
 	signals.objectChanged.add( function ( object ) {
 	signals.objectChanged.add( function ( object ) {
 
 
-		selectionBox.update( object );
-		transformControls.update();
+		if ( editor.selected === object ) {
+
+			selectionBox.update( object );
+			transformControls.update();
+
+		}
 
 
 		if ( object instanceof THREE.PerspectiveCamera ) {
 		if ( object instanceof THREE.PerspectiveCamera ) {
 
 
@@ -405,18 +431,12 @@ var Viewport = function ( editor ) {
 
 
 	signals.objectRemoved.add( function ( object ) {
 	signals.objectRemoved.add( function ( object ) {
 
 
-		var materialsNeedUpdate = false;
-
 		object.traverse( function ( child ) {
 		object.traverse( function ( child ) {
 
 
-			if ( child instanceof THREE.Light ) materialsNeedUpdate = true;
-
 			objects.splice( objects.indexOf( child ), 1 );
 			objects.splice( objects.indexOf( child ), 1 );
 
 
 		} );
 		} );
 
 
-		if ( materialsNeedUpdate === true ) updateMaterials();
-
 	} );
 	} );
 
 
 	signals.helperAdded.add( function ( object ) {
 	signals.helperAdded.add( function ( object ) {
@@ -455,8 +475,6 @@ var Viewport = function ( editor ) {
 
 
 			}
 			}
 
 
-			updateMaterials();
-
 			oldFogType = fogType;
 			oldFogType = fogType;
 
 
 		}
 		}
@@ -489,6 +507,11 @@ var Viewport = function ( editor ) {
 
 
 	signals.windowResize.add( function () {
 	signals.windowResize.add( function () {
 
 
+		// TODO: Move this out?
+
+		editor.DEFAULT_CAMERA.aspect = container.dom.offsetWidth / container.dom.offsetHeight;
+		editor.DEFAULT_CAMERA.updateProjectionMatrix();
+
 		camera.aspect = container.dom.offsetWidth / container.dom.offsetHeight;
 		camera.aspect = container.dom.offsetWidth / container.dom.offsetHeight;
 		camera.updateProjectionMatrix();
 		camera.updateProjectionMatrix();
 
 
@@ -513,30 +536,6 @@ var Viewport = function ( editor ) {
 
 
 	//
 	//
 
 
-	function updateMaterials() {
-
-		editor.scene.traverse( function ( node ) {
-
-			if ( node.material ) {
-
-				node.material.needsUpdate = true;
-
-				if ( node.material instanceof THREE.MeshFaceMaterial ) {
-
-					for ( var i = 0; i < node.material.materials.length; i ++ ) {
-
-						node.material.materials[ i ].needsUpdate = true;
-
-					}
-
-				}
-
-			}
-
-		} );
-
-	}
-
 	function updateFog( root ) {
 	function updateFog( root ) {
 
 
 		if ( root.fog ) {
 		if ( root.fog ) {
@@ -601,4 +600,4 @@ var Viewport = function ( editor ) {
 
 
 	return container;
 	return container;
 
 
-}
+};

+ 66 - 0
editor/js/commands/AddObjectCommand.js

@@ -0,0 +1,66 @@
+/**
+ * @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 = function ( object ) {
+
+	Command.call( this );
+
+	this.type = 'AddObjectCommand';
+
+	this.object = object;
+	if ( object !== undefined ) {
+
+		this.name = 'Add Object: ' + object.name;
+
+	}
+
+};
+
+AddObjectCommand.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 );
+
+		}
+
+	}
+
+};

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