Browse Source

Merge branch 'dev' into webglrendertarget-refactor

Marius Kintel 9 years ago
parent
commit
419478eaa1
100 changed files with 4758 additions and 2364 deletions
  1. 1 0
      .gitignore
  2. 8 8
      bower.json
  3. 276 427
      build/three.js
  4. 387 346
      build/three.min.js
  5. 2 7
      docs/api/core/Geometry.html
  6. 2 2
      docs/api/examples/SpriteCanvasMaterial.html
  7. 0 88
      docs/api/extras/ImageUtils.html
  8. 0 20
      docs/api/extras/geometries/CubeGeometry.html
  9. 1 7
      docs/api/lights/DirectionalLight.html
  10. 0 6
      docs/api/lights/SpotLight.html
  11. 20 8
      docs/api/materials/MeshPhongMaterial.html
  12. 9 9
      docs/api/math/Box2.html
  13. 14 14
      docs/api/math/Box3.html
  14. 7 16
      docs/api/math/Math.html
  15. 4 4
      docs/api/math/Plane.html
  16. 9 9
      docs/api/math/Ray.html
  17. 11 13
      docs/api/textures/CubeTexture.html
  18. 1 0
      docs/index.html
  19. 2 4
      docs/list.js
  20. 34 33
      docs/scenes/bones-browser.html
  21. 34 31
      docs/scenes/geometry-browser.html
  22. 1 1
      docs/scenes/js/material.js
  23. 28 27
      docs/scenes/material-browser.html
  24. 24 16
      editor/css/dark.css
  25. 24 16
      editor/css/light.css
  26. 18 6
      editor/css/main.css
  27. 132 0
      editor/docs/Implementing additional commands for undo-redo.md
  28. 94 0
      editor/docs/Writing unit tests for undo-redo commands.md
  29. 46 20
      editor/index.html
  30. 47 0
      editor/js/Command.js
  31. 3 0
      editor/js/Config.js
  32. 34 11
      editor/js/Editor.js
  33. 277 35
      editor/js/History.js
  34. 37 33
      editor/js/Loader.js
  35. 17 33
      editor/js/Menubar.Add.js
  36. 59 14
      editor/js/Menubar.Edit.js
  37. 21 21
      editor/js/Menubar.File.js
  38. 11 10
      editor/js/Menubar.Status.js
  39. 66 17
      editor/js/Script.js
  40. 5 9
      editor/js/Sidebar.Geometry.BoxGeometry.js
  41. 3 1
      editor/js/Sidebar.Geometry.BufferGeometry.js
  42. 28 10
      editor/js/Sidebar.Geometry.CircleGeometry.js
  43. 5 9
      editor/js/Sidebar.Geometry.CylinderGeometry.js
  44. 3 1
      editor/js/Sidebar.Geometry.Geometry.js
  45. 5 7
      editor/js/Sidebar.Geometry.IcosahedronGeometry.js
  46. 3 1
      editor/js/Sidebar.Geometry.Modifiers.js
  47. 5 9
      editor/js/Sidebar.Geometry.PlaneGeometry.js
  48. 5 9
      editor/js/Sidebar.Geometry.SphereGeometry.js
  49. 5 9
      editor/js/Sidebar.Geometry.TorusGeometry.js
  50. 5 9
      editor/js/Sidebar.Geometry.TorusKnotGeometry.js
  51. 19 20
      editor/js/Sidebar.Geometry.js
  52. 136 0
      editor/js/Sidebar.History.js
  53. 282 57
      editor/js/Sidebar.Material.js
  54. 118 43
      editor/js/Sidebar.Object3D.js
  55. 33 16
      editor/js/Sidebar.Project.js
  56. 4 5
      editor/js/Sidebar.Script.js
  57. 1 0
      editor/js/Sidebar.js
  58. 2 2
      editor/js/Storage.js
  59. 5 9
      editor/js/Toolbar.js
  60. 43 57
      editor/js/Viewport.js
  61. 66 0
      editor/js/commands/AddObjectCommand.js
  62. 76 0
      editor/js/commands/AddScriptCommand.js
  63. 107 0
      editor/js/commands/MoveObjectCommand.js
  64. 85 0
      editor/js/commands/MultiCmdsCommand.js
  65. 103 0
      editor/js/commands/RemoveObjectCommand.js
  66. 81 0
      editor/js/commands/RemoveScriptCommand.js
  67. 74 0
      editor/js/commands/SetColorCommand.js
  68. 86 0
      editor/js/commands/SetGeometryCommand.js
  69. 71 0
      editor/js/commands/SetGeometryValueCommand.js
  70. 74 0
      editor/js/commands/SetMaterialColorCommand.js
  71. 74 0
      editor/js/commands/SetMaterialCommand.js
  72. 125 0
      editor/js/commands/SetMaterialMapCommand.js
  73. 76 0
      editor/js/commands/SetMaterialValueCommand.js
  74. 83 0
      editor/js/commands/SetPositionCommand.js
  75. 84 0
      editor/js/commands/SetRotationCommand.js
  76. 84 0
      editor/js/commands/SetScaleCommand.js
  77. 100 0
      editor/js/commands/SetSceneCommand.js
  78. 88 0
      editor/js/commands/SetScriptValueCommand.js
  79. 71 0
      editor/js/commands/SetUuidCommand.js
  80. 76 0
      editor/js/commands/SetValueCommand.js
  81. 21 21
      editor/js/libs/app.js
  82. 0 160
      editor/js/libs/tern-threejs/threejs.js
  83. 103 74
      editor/js/libs/ui.js
  84. 33 2
      editor/js/libs/ui.three.js
  85. 1 1
      examples/canvas_geometry_panorama_fisheye.html
  86. 8 4
      examples/canvas_geometry_text.html
  87. 0 1
      examples/canvas_materials.html
  88. 0 238
      examples/canvas_materials_depth.html
  89. 10 4
      examples/canvas_morphtargets_horse.html
  90. 12 3
      examples/index.html
  91. 33 57
      examples/js/AnimationClipCreator.js
  92. 23 28
      examples/js/BlendCharacter.js
  93. 3 12
      examples/js/BlendCharacterGui.js
  94. 26 12
      examples/js/MD2Character.js
  95. 3 4
      examples/js/MorphAnimMesh.js
  96. 25 110
      examples/js/ShaderSkin.js
  97. 11 33
      examples/js/ShaderTerrain.js
  98. 281 0
      examples/js/TimelinerController.js
  99. 1 1
      examples/js/UCSCharacter.js
  100. 4 4
      examples/js/controls/EditorControls.js

+ 1 - 0
.gitignore

@@ -2,3 +2,4 @@
 *.swp
 *.swp
 .project
 .project
 node_modules
 node_modules
+.idea/

+ 8 - 8
bower.json

@@ -14,13 +14,13 @@
 	"ignore": [
 	"ignore": [
 		"**/.*",
 		"**/.*",
 		"*.md",
 		"*.md",
-		"docs",
-		"editor",
-		"examples/*",
-		"!examples/js",
-		"src",
-		"test",
-		"utils",
-		"LICENSE"
+		"/docs",
+		"/editor",
+		"/examples/*",
+		"!/examples/js",
+		"/src",
+		"/test",
+		"/utils",
+		"/LICENSE"
 	]
 	]
 }
 }

File diff suppressed because it is too large
+ 276 - 427
build/three.js


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


+ 2 - 7
docs/api/core/Geometry.html

@@ -88,13 +88,6 @@
 		Morph vertices match number and order of primary vertices.
 		Morph vertices match number and order of primary vertices.
 		</div>
 		</div>
 
 
-		<h3>[property:Array morphColors]</h3>
-		<div>
-		Array of morph colors. Morph colors have similar structure as morph targets, each color set is a Javascript object:
-		<code>morphColor = { name: "colorName", colors: [ new THREE.Color(), ... ] }</code>
-		Morph colors can match either the number and order of faces (face colors) or the number of vertices (vertex colors).
-		</div>
-
 		<h3>[property:Array morphNormals]</h3>
 		<h3>[property:Array morphNormals]</h3>
 		<div>
 		<div>
 		Array of morph normals. Morph normals have similar structure as morph targets, each normal set is a Javascript object:
 		Array of morph normals. Morph normals have similar structure as morph targets, each normal set is a Javascript object:
@@ -285,6 +278,8 @@
 		<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>

+ 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 - 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>

+ 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>

+ 1 - 7
docs/api/lights/DirectionalLight.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>
@@ -123,12 +123,6 @@ scene.add( directionalLight );</code>
 			Default — *0*.
 			Default — *0*.
 		</div>
 		</div>
 
 
-		<h3>[property:Float shadowDarkness]</h3>
-		<div>
-			Darkness of shadow casted by this light (from *0* to *1*).<br />
-			Default — *0.5*.
-		</div>
-
 		<h3>[property:Integer shadowMapWidth]</h3>
 		<h3>[property:Integer shadowMapWidth]</h3>
 		<div>
 		<div>
 			Shadow map texture width in pixels.<br />
 			Shadow map texture width in pixels.<br />

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

@@ -146,12 +146,6 @@
 			Default — *0*.
 			Default — *0*.
 		</div>
 		</div>
 
 
-		<h3>[property:Float shadowDarkness]</h3>
-		<div>
-			Darkness of shadow casted by this light (from *0* to *1*).<br />
-			Default — *0.5*.
-		</div>
-
 		<h3>[property:Integer shadowMapWidth]</h3>
 		<h3>[property:Integer shadowMapWidth]</h3>
 		<div>
 		<div>
 			Shadow map texture width in pixels.<br />
 			Shadow map texture width in pixels.<br />

+ 20 - 8
docs/api/materials/MeshPhongMaterial.html

@@ -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 />
@@ -68,14 +71,7 @@
 		</div>
 		</div>
 
 
 		<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*. It should not be set to 0.</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>
+		<div>How shiny the specular highlight is; a higher value gives a sharper highlight. Default is *30*.</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>
@@ -119,6 +115,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>
 
 

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

@@ -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

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

@@ -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

+ 7 - 16
docs/api/math/Math.html

@@ -16,23 +16,14 @@
 
 
 		<h2>Methods</h2>
 		<h2>Methods</h2>
 
 
-		<h3>[method:Float clamp]( [page:Float x], [page:Float a], [page:Float b] )</h3>
+		<h3>[method:Float clamp]( [page:Float value], [page:Float min], [page:Float max] )</h3>
 		<div>
 		<div>
-		x — Value to be clamped.<br />
-		a — Minimum value<br />
-		b — Maximum value.
+		value — Value to be clamped.<br />
+		min — Minimum value<br />
+		max — Maximum value.
 		</div>
 		</div>
 		<div>
 		<div>
-		Clamps the *x* to be between *a* and *b*.
-		</div>
-
-		<h3>[method:Float clampBottom]( [page:Float x], [page:Float a] )</h3>
-		<div>
-		x — Value to be clamped.<br />
-		a — Minimum value
-		</div>
-		<div>
-		Clamps the *x* to be larger than *a*.
+		Clamps the *value* to be between *min* and *max*.
 		</div>
 		</div>
 
 
 		<h3>[method:Float mapLinear]( [page:Float x], [page:Float a1], [page:Float a2], [page:Float b1], [page:Float b2] )</h3>
 		<h3>[method:Float mapLinear]( [page:Float x], [page:Float a1], [page:Float a2], [page:Float b1], [page:Float b2] )</h3>
@@ -97,7 +88,7 @@
 		</div>
 		</div>
 		<div>
 		<div>
 		Returns a value between 0-1 that represents the percentage that x has moved between min and max, but smoothed or slowed down the closer X is to the min and max.<br/><br/>
 		Returns a value between 0-1 that represents the percentage that x has moved between min and max, but smoothed or slowed down the closer X is to the min and max.<br/><br/>
-		
+
 		[link:http://en.wikipedia.org/wiki/Smoothstep Wikipedia]
 		[link:http://en.wikipedia.org/wiki/Smoothstep Wikipedia]
 		</div>
 		</div>
 
 
@@ -110,7 +101,7 @@
 		<div>
 		<div>
 		Returns a value between 0-1. It works the same as smoothstep, but more smooth.
 		Returns a value between 0-1. It works the same as smoothstep, but more smooth.
 		</div>
 		</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]

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

@@ -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>

+ 9 - 9
docs/api/math/Ray.html

@@ -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>

+ 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>
 
 
 
 

+ 1 - 0
docs/index.html

@@ -417,5 +417,6 @@
 			].join('\n'));
 			].join('\n'));
 
 
 		</script>
 		</script>
+		<script src="../build/three.min.js"></script> <!-- console sandbox -->
 	</body>
 	</body>
 </html>
 </html>

+ 2 - 4
docs/list.js

@@ -79,7 +79,6 @@ var list = {
 			[ "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" ]
 		],
 		],
 
 
@@ -160,7 +159,6 @@ var list = {
 		"Extras": [
 		"Extras": [
 			[ "FontUtils", "api/extras/FontUtils" ],
 			[ "FontUtils", "api/extras/FontUtils" ],
 			[ "GeometryUtils", "api/extras/GeometryUtils" ],
 			[ "GeometryUtils", "api/extras/GeometryUtils" ],
-			[ "ImageUtils", "api/extras/ImageUtils" ],
 			[ "SceneUtils", "api/extras/SceneUtils" ]
 			[ "SceneUtils", "api/extras/SceneUtils" ]
 		],
 		],
 
 
@@ -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" ]
 
 
 		]
 		]
 
 

+ 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>

+ 34 - 31
docs/scenes/geometry-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,12 +31,14 @@
 		</style>
 		</style>
 	</head>
 	</head>
 	<body>
 	<body>
-		
+
 		<a id='newWindow' href='./geometry-browser.html' target='_blank'>Open in New Window</a>
 		<a id='newWindow' href='./geometry-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 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_bold.typeface.js"></script>
 		<script src="../../examples/fonts/gentilis_regular.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_bold.typeface.js"></script>
@@ -45,22 +47,23 @@
 		<script src="../../examples/fonts/helvetiker_regular.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_regular.typeface.js"></script>
 		<script src="../../examples/fonts/droid/droid_serif_bold.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>
-		
+
 		<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({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 );
-			
+
 			var orbit = new THREE.OrbitControls( camera, renderer.domElement );
 			var orbit = new THREE.OrbitControls( camera, renderer.domElement );
 			orbit.enableZoom = false;
 			orbit.enableZoom = false;
 
 
@@ -71,7 +74,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 );
@@ -81,40 +84,40 @@
 			scene.add( lights[2] );
 			scene.add( lights[2] );
 
 
 			var mesh = new THREE.Object3D()
 			var mesh = new THREE.Object3D()
-			
+
 			mesh.add( new THREE.LineSegments(
 			mesh.add( new THREE.LineSegments(
-				
+
 				new THREE.Geometry(),
 				new THREE.Geometry(),
-				
+
 				new THREE.LineBasicMaterial({
 				new THREE.LineBasicMaterial({
 					color: 0xffffff,
 					color: 0xffffff,
 					transparent: true,
 					transparent: true,
 					opacity: 0.5
 					opacity: 0.5
 				})
 				})
-				
+
 			));
 			));
-			
+
 			mesh.add( new THREE.Mesh(
 			mesh.add( new THREE.Mesh(
-				
+
 				new THREE.Geometry(),
 				new THREE.Geometry(),
-				
+
 				new THREE.MeshPhongMaterial({
 				new THREE.MeshPhongMaterial({
 					color: 0x156289,
 					color: 0x156289,
 					emissive: 0x072534,
 					emissive: 0x072534,
 					side: THREE.DoubleSide,
 					side: THREE.DoubleSide,
 					shading: THREE.FlatShading
 					shading: THREE.FlatShading
 				})
 				})
-				
+
 			));
 			));
-			
+
 			var options = chooseFromHash( mesh );
 			var options = chooseFromHash( mesh );
-			
+
 			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;
@@ -125,20 +128,20 @@
 				}
 				}
 
 
 				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>

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

@@ -439,7 +439,7 @@ function guiMeshPhongMaterial ( gui, mesh, material, geometry ) {
 	folder.addColor( data, 'emissive' ).onChange( handleColorChange( material.emissive ) );
 	folder.addColor( data, 'emissive' ).onChange( handleColorChange( material.emissive ) );
 	folder.addColor( data, 'specular' ).onChange( handleColorChange( material.specular ) );
 	folder.addColor( data, 'specular' ).onChange( handleColorChange( material.specular ) );
 
 
-	folder.add( material, 'shininess', 1, 100);
+	folder.add( material, 'shininess', 0, 100);
 	folder.add( material, 'shading', constants.shading).onChange( needsUpdate( material, geometry ) );
 	folder.add( material, 'shading', constants.shading).onChange( needsUpdate( material, geometry ) );
 	folder.add( material, 'wireframe' );
 	folder.add( material, 'wireframe' );
 	folder.add( material, 'wireframeLinewidth', 0, 10 );
 	folder.add( material, 'wireframeLinewidth', 0, 10 );

+ 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>

+ 24 - 16
editor/css/dark.css

@@ -39,20 +39,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 +61,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 +71,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,20 +86,21 @@ input.Number {
 	#menubar .menu.right {
 	#menubar .menu.right {
 		float: right;
 		float: right;
 		cursor: auto;
 		cursor: auto;
+		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;
 		}
 		}
@@ -116,7 +117,7 @@ input.Number {
 				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 {
@@ -128,11 +129,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;
@@ -155,7 +163,7 @@ input.Number {
 	}
 	}
 
 
 	#sidebar .Panel.collapsed {
 	#sidebar .Panel.collapsed {
-		margin-bottom: 0px;
+		margin-bottom: 0;
 	}
 	}
 
 
 	#sidebar > .Panel {
 	#sidebar > .Panel {
@@ -172,9 +180,9 @@ input.Number {
 
 
 #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;

+ 24 - 16
editor/css/light.css

@@ -32,20 +32,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 +54,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 +64,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,20 +79,21 @@ input.Number {
 	#menubar .menu.right {
 	#menubar .menu.right {
 		float: right;
 		float: right;
 		cursor: auto;
 		cursor: auto;
+		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;
 		}
 		}
@@ -109,7 +110,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 {
@@ -122,11 +123,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;
@@ -148,7 +156,7 @@ input.Number {
 	}
 	}
 
 
 	#sidebar .Panel.collapsed {
 	#sidebar .Panel.collapsed {
-		margin-bottom: 0px;
+		margin-bottom: 0;
 	}
 	}
 
 
 	#sidebar > .Panel {
 	#sidebar > .Panel {
@@ -159,9 +167,9 @@ input.Number {
 
 
 #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;

+ 18 - 6
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,7 +88,7 @@ textarea, input { outline: none; } /* osx */
 
 
 		color: #f00;
 		color: #f00;
 		text-align: right;
 		text-align: right;
-		padding: 0px 20px;
+		padding: 0 20px;
 
 
 	}
 	}
 
 
@@ -97,7 +97,7 @@ textarea, input { outline: none; } /* osx */
 .type {
 .type {
 	position:relative;
 	position:relative;
 	top:-2px;
 	top:-2px;
-	padding: 0px 2px;
+	padding: 0 2px;
 	color: #ddd;
 	color: #ddd;
 }
 }
 .type:after {
 .type:after {
@@ -116,6 +116,18 @@ textarea, input { outline: none; } /* osx */
 	color: #8888ee;
 	color: #8888ee;
 }
 }
 
 
+.Line {
+	color: #88ee88;
+}
+
+.LineSegments {
+	color: #88ee88;
+}
+
+.Points {
+	color: #ee8888;
+}
+
 /* */
 /* */
 
 
 .PointLight {
 .PointLight {

+ 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.

+ 46 - 20
editor/index.html

@@ -23,6 +23,7 @@
 		<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>
@@ -119,9 +120,31 @@
 		<script src="js/Sidebar.Geometry.TeapotBufferGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.TeapotBufferGeometry.js"></script>
 		<script src="js/Sidebar.Material.js"></script>
 		<script src="js/Sidebar.Material.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>
 		<script>
 
 
@@ -154,10 +177,8 @@
 			var sidebar = new Sidebar( editor );
 			var sidebar = new Sidebar( editor );
 			document.body.appendChild( sidebar.dom );
 			document.body.appendChild( sidebar.dom );
 
 
-			/*
-			var dialog = new UI.Dialog();
-			document.body.appendChild( dialog.dom );
-			*/
+			var modal = new UI.Modal();
+			document.body.appendChild( modal.dom );
 
 
 			//
 			//
 
 
@@ -187,7 +208,7 @@
 
 
 				var timeout;
 				var timeout;
 
 
-				var saveState = function ( scene ) {
+				function saveState( scene ) {
 
 
 					if ( editor.config.getKey( 'autosave' ) === false ) {
 					if ( editor.config.getKey( 'autosave' ) === false ) {
 
 
@@ -215,7 +236,6 @@
 
 
 				var signals = editor.signals;
 				var signals = editor.signals;
 
 
-				signals.editorCleared.add( saveState );
 				signals.geometryChanged.add( saveState );
 				signals.geometryChanged.add( saveState );
 				signals.objectAdded.add( saveState );
 				signals.objectAdded.add( saveState );
 				signals.objectChanged.add( saveState );
 				signals.objectChanged.add( saveState );
@@ -223,19 +243,13 @@
 				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 );
 
 
-				/*
-				var showDialog = function ( content ) {
+				signals.showModal.add( function ( content ) {
 
 
-					dialog.clear();
+					modal.show( content );
 
 
-					dialog.add( content );
-					dialog.showModal();
-
-				};
-
-				signals.showDialog.add( showDialog );
-				*/
+				} );
 
 
 			} );
 			} );
 
 
@@ -272,20 +286,32 @@
 						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;
 						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;
+
 				}
 				}
 
 
 			}, false );
 			}, false );
 
 
-			var onWindowResize = function ( event ) {
+			function onWindowResize( event ) {
 
 
 				editor.signals.windowResize.dispatch();
 				editor.signals.windowResize.dispatch();
 
 
-			};
+			}
 
 
 			window.addEventListener( 'resize', onWindowResize, false );
 			window.addEventListener( 'resize', onWindowResize, false );
 
 

+ 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 - 0
editor/js/Config.js

@@ -10,12 +10,15 @@ var Config = function () {
 		'autosave': true,
 		'autosave': true,
 		'theme': 'css/light.css',
 		'theme': 'css/light.css',
 
 
+		'project/history/stored': true,
 		'project/renderer': 'WebGLRenderer',
 		'project/renderer': 'WebGLRenderer',
 		'project/renderer/antialias': true,
 		'project/renderer/antialias': true,
+		'project/renderer/shadows': true,
 		'project/vr': false,
 		'project/vr': false,
 
 
 		'ui/sidebar/animation/collapsed': true,
 		'ui/sidebar/animation/collapsed': true,
 		'ui/sidebar/geometry/collapsed': true,
 		'ui/sidebar/geometry/collapsed': true,
+		'ui/sidebar/history/collapsed': true,
 		'ui/sidebar/material/collapsed': true,
 		'ui/sidebar/material/collapsed': true,
 		'ui/sidebar/object3d/collapsed': false,
 		'ui/sidebar/object3d/collapsed': false,
 		'ui/sidebar/project/collapsed': true,
 		'ui/sidebar/project/collapsed': true,

+ 34 - 11
editor/js/Editor.js

@@ -19,7 +19,7 @@ var Editor = function () {
 
 
 		// actions
 		// actions
 
 
-		// showDialog: new SIGNALS.Signal(),
+		showModal: new SIGNALS.Signal(),
 
 
 		// notifications
 		// notifications
 
 
@@ -62,7 +62,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()
 
 
 	};
 	};
 
 
@@ -102,14 +105,6 @@ Editor.prototype = {
 
 
 	},
 	},
 
 
-	/*
-	showDialog: function ( value ) {
-
-		this.signals.showDialog.dispatch( value );
-
-	},
-	*/
-
 	//
 	//
 
 
 	setScene: function ( scene ) {
 	setScene: function ( scene ) {
@@ -411,6 +406,7 @@ Editor.prototype = {
 	clear: function () {
 	clear: function () {
 
 
 		this.history.clear();
 		this.history.clear();
+		this.storage.clear();
 
 
 		this.camera.position.set( 500, 250, 500 );
 		this.camera.position.set( 500, 250, 500 );
 		this.camera.lookAt( new THREE.Vector3() );
 		this.camera.lookAt( new THREE.Vector3() );
@@ -461,6 +457,7 @@ Editor.prototype = {
 
 
 		this.setScene( loader.parse( json.scene ) );
 		this.setScene( loader.parse( json.scene ) );
 		this.scripts = json.scripts;
 		this.scripts = json.scripts;
+		this.history.fromJSON( json.history );
 
 
 	},
 	},
 
 
@@ -469,14 +466,40 @@ Editor.prototype = {
 		return {
 		return {
 
 
 			project: {
 			project: {
+				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( 'project/history/stored' ) ) {
+
+			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( 'project/history/stored' ) ) {
+
+			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 );
 
 
 	}
 	}
 
 

+ 37 - 33
editor/js/Loader.js

@@ -24,8 +24,7 @@ var Loader = function ( editor ) {
 					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 );
@@ -40,7 +39,7 @@ var Loader = function ( editor ) {
 					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 );
@@ -58,7 +57,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 );
@@ -81,8 +80,7 @@ var Loader = function ( editor ) {
 					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 );
@@ -110,8 +108,7 @@ var Loader = function ( editor ) {
 						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 ) );
 
 
 					} );
 					} );
 
 
@@ -132,8 +129,7 @@ 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 );
 				}, false );
 				reader.readAsText( file );
 				reader.readAsText( file );
@@ -208,8 +204,7 @@ 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 );
 					}, false );
 					reader.readAsArrayBuffer( file );
 					reader.readAsArrayBuffer( file );
@@ -233,8 +228,7 @@ var Loader = function ( editor ) {
 						mesh.mixer = new THREE.AnimationMixer( mesh )
 						mesh.mixer = new THREE.AnimationMixer( mesh )
 						mesh.name = filename;
 						mesh.name = filename;
 
 
-						editor.addObject( mesh );
-						editor.select( mesh );
+						editor.execute( new AddObjectCommand( mesh ) );
 
 
 					}, false );
 					}, false );
 					reader.readAsArrayBuffer( file );
 					reader.readAsArrayBuffer( file );
@@ -251,8 +245,25 @@ 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':
+
+				var reader = new FileReader();
+				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 );
@@ -275,8 +286,7 @@ var Loader = function ( editor ) {
 					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 );
@@ -299,8 +309,7 @@ var Loader = function ( editor ) {
 					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 );
 
 
@@ -329,8 +338,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 );
@@ -354,8 +362,7 @@ var Loader = function ( editor ) {
 					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 );
@@ -371,7 +378,7 @@ var Loader = function ( editor ) {
 
 
 					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 );
@@ -415,8 +422,7 @@ var Loader = function ( editor ) {
 
 
 			var mesh = new THREE.Mesh( result );
 			var mesh = new THREE.Mesh( result );
 
 
-			editor.addObject( mesh );
-			editor.select( mesh );
+			editor.execute( new AddObjectCommand( mesh ) );
 
 
 		} else if ( data.metadata.type.toLowerCase() === 'geometry' ) {
 		} else if ( data.metadata.type.toLowerCase() === 'geometry' ) {
 
 
@@ -463,8 +469,7 @@ var Loader = function ( editor ) {
 
 
 			mesh.name = filename;
 			mesh.name = filename;
 
 
-			editor.addObject( mesh );
-			editor.select( mesh );
+			editor.execute( new AddObjectCommand( mesh ) );
 
 
 		} else if ( data.metadata.type.toLowerCase() === 'object' ) {
 		} else if ( data.metadata.type.toLowerCase() === 'object' ) {
 
 
@@ -475,12 +480,11 @@ var Loader = function ( editor ) {
 
 
 			if ( result instanceof THREE.Scene ) {
 			if ( result instanceof THREE.Scene ) {
 
 
-				editor.setScene( result );
+				editor.execute( new SetSceneCommand( result ) );
 
 
 			} else {
 			} else {
 
 
-				editor.addObject( result );
-				editor.select( result );
+				editor.execute( new AddObjectCommand( result ) );
 
 
 			}
 			}
 
 
@@ -491,7 +495,7 @@ var Loader = function ( editor ) {
 			var loader = new THREE.SceneLoader();
 			var loader = new THREE.SceneLoader();
 			loader.parse( data, function ( result ) {
 			loader.parse( data, function ( result ) {
 
 
-				editor.setScene( result.scene );
+				editor.execute( new SetSceneCommand( result.scene ) );
 
 
 			}, '' );
 			}, '' );
 
 

+ 17 - 33
editor/js/Menubar.Add.js

@@ -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 );
@@ -68,8 +67,7 @@ Menubar.Add = function ( editor ) {
 		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 );
@@ -93,8 +91,7 @@ Menubar.Add = function ( editor ) {
 		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
 		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
 		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 );
@@ -113,8 +110,7 @@ Menubar.Add = function ( editor ) {
 		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
 		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
 		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 );
@@ -137,8 +133,7 @@ Menubar.Add = function ( editor ) {
 		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
 		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
 		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 );
@@ -162,8 +157,7 @@ Menubar.Add = function ( editor ) {
 		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
 		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
 		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 );
@@ -182,8 +176,7 @@ Menubar.Add = function ( editor ) {
 		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
 		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
 		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 );
@@ -205,8 +198,7 @@ Menubar.Add = function ( editor ) {
 		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
 		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
 		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 );
@@ -230,8 +222,7 @@ Menubar.Add = function ( editor ) {
 		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
 		var mesh = new THREE.Mesh( geometry, new THREE.MeshPhongMaterial() );
 		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 );
@@ -276,8 +267,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 );
@@ -300,8 +290,7 @@ 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 );
@@ -325,8 +314,7 @@ Menubar.Add = function ( editor ) {
 
 
 		light.position.set( 0.5, 1, 0.75 ).multiplyScalar( 200 );
 		light.position.set( 0.5, 1, 0.75 ).multiplyScalar( 200 );
 
 
-		editor.addObject( light );
-		editor.select( light );
+		editor.execute( new AddObjectCommand( light ) );
 
 
 	} );
 	} );
 	options.add( option );
 	options.add( option );
@@ -347,8 +335,7 @@ Menubar.Add = function ( editor ) {
 
 
 		light.position.set( 0.5, 1, 0.75 ).multiplyScalar( 200 );
 		light.position.set( 0.5, 1, 0.75 ).multiplyScalar( 200 );
 
 
-		editor.addObject( light );
-		editor.select( light );
+		editor.execute( new AddObjectCommand( light ) );
 
 
 	} );
 	} );
 	options.add( option );
 	options.add( option );
@@ -367,10 +354,9 @@ 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, 100, 0 );
 
 
-		editor.addObject( light );
-		editor.select( light );
+		editor.execute( new AddObjectCommand( light ) );
 
 
 	} );
 	} );
 	options.add( option );
 	options.add( option );
@@ -387,8 +373,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 );
@@ -407,8 +392,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 );

+ 59 - 14
editor/js/Menubar.Edit.js

@@ -18,28 +18,66 @@ Menubar.Edit = function ( editor ) {
 
 
 	// Undo
 	// Undo
 
 
-	var option = new UI.Panel();
-	option.setClass( 'option' );
-	option.setTextContent( 'Undo' );
-	option.onClick( function () {
+	var undo = new UI.Panel();
+	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 redo = new UI.Panel();
+	redo.setClass( 'option' );
+	redo.setTextContent( 'Redo (Ctrl+Shift+Z)' );
+	redo.onClick( function () {
+
+		editor.redo();
+
+	} );
+	options.add( redo );
+
+	// Clear History
+
 	var option = new UI.Panel();
 	var option = new UI.Panel();
 	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() );
@@ -57,8 +95,7 @@ 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 );
@@ -75,8 +112,9 @@ 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 );
@@ -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" ) );
 
 

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

@@ -86,8 +86,13 @@ Menubar.File = function ( editor ) {
 		}
 		}
 
 
 		var output = geometry.toJSON();
 		var output = geometry.toJSON();
-		output = JSON.stringify( output, null, '\t' );
-		output = output.replace( /[\n\t]+([\d\.e\-\[\]]+)/g, '$1' );
+
+		try {
+			output = JSON.stringify( output, null, '\t' );
+			output = output.replace( /[\n\t]+([\d\.e\-\[\]]+)/g, '$1' );
+		} catch ( e ) {
+			output = JSON.stringify( output );
+		}
 
 
 		exportString( output, 'geometry.json' );
 		exportString( output, 'geometry.json' );
 
 
@@ -111,8 +116,13 @@ Menubar.File = function ( editor ) {
 		}
 		}
 
 
 		var output = object.toJSON();
 		var output = object.toJSON();
-		output = JSON.stringify( output, null, '\t' );
-		output = output.replace( /[\n\t]+([\d\.e\-\[\]]+)/g, '$1' );
+
+		try {
+			output = JSON.stringify( output, null, '\t' );
+			output = output.replace( /[\n\t]+([\d\.e\-\[\]]+)/g, '$1' );
+		} catch ( e ) {
+			output = JSON.stringify( output );
+		}
 
 
 		exportString( output, 'model.json' );
 		exportString( output, 'model.json' );
 
 
@@ -127,8 +137,13 @@ Menubar.File = function ( editor ) {
 	option.onClick( function () {
 	option.onClick( function () {
 
 
 		var output = editor.scene.toJSON();
 		var output = editor.scene.toJSON();
-		output = JSON.stringify( output, null, '\t' );
-		output = output.replace( /[\n\t]+([\d\.e\-\[\]]+)/g, '$1' );
+
+		try {
+			output = JSON.stringify( output, null, '\t' );
+			output = output.replace( /[\n\t]+([\d\.e\-\[\]]+)/g, '$1' );
+		} catch ( e ) {
+			output = JSON.stringify( output );
+		}
 
 
 		exportString( output, 'scene.json' );
 		exportString( output, 'scene.json' );
 
 
@@ -260,21 +275,6 @@ Menubar.File = function ( editor ) {
 	} );
 	} );
 	options.add( option );
 	options.add( option );
 
 
-	/*
-	// Test
-
-	var option = new UI.Panel();
-	option.setClass( 'option' );
-	option.setTextContent( 'Test' );
-	option.onClick( function () {
-
-		var text = new UI.Text( 'blah' );
-		editor.showDialog( text );
-
-	} );
-	options.add( option );
-	*/
-
 
 
 	//
 	//
 
 

+ 11 - 10
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,25 +22,25 @@ Menubar.Status = function ( editor ) {
 		}
 		}
 
 
 	} );
 	} );
-	container.add( checkbox );
-
-	var title = new UI.Panel();
-	title.setClass( 'title' );
-	title.setTextContent( 'Autosave' );
-	container.add( title );
+	container.add( autosave );
 
 
 	editor.signals.savingStarted.add( function () {
 	editor.signals.savingStarted.add( function () {
 
 
-		title.setTextDecoration( 'underline' );
+		autosave.text.setTextDecoration( 'underline' );
 
 
 	} );
 	} );
 
 
 	editor.signals.savingFinished.add( function () {
 	editor.signals.savingFinished.add( function () {
 
 
-		title.setTextDecoration( 'none' );
+		autosave.text.setTextDecoration( 'none' );
 
 
 	} );
 	} );
 
 
+	var version = new UI.Text( 'r' + THREE.REVISION );
+	version.setClass( 'title' );
+	version.setOpacity( 0.5 );
+	container.add( version );
+
 	return container;
 	return container;
 
 
 };
 };

+ 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;
 
 
 };
 };

+ 5 - 9
editor/js/Sidebar.Geometry.BoxGeometry.js

@@ -2,7 +2,9 @@
  * @author mrdoob / http://mrdoob.com/
  * @author mrdoob / http://mrdoob.com/
  */
  */
 
 
-Sidebar.Geometry.BoxGeometry = function ( signals, object ) {
+Sidebar.Geometry.BoxGeometry = function ( editor, object ) {
+
+	var signals = editor.signals;
 
 
 	var container = new UI.Panel();
 	var container = new UI.Panel();
 
 
@@ -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 );
+		) ) );
 
 
 	}
 	}
 
 

+ 3 - 1
editor/js/Sidebar.Geometry.BufferGeometry.js

@@ -2,7 +2,9 @@
  * @author mrdoob / http://mrdoob.com/
  * @author mrdoob / http://mrdoob.com/
  */
  */
 
 
-Sidebar.Geometry.BufferGeometry = function ( signals ) {
+Sidebar.Geometry.BufferGeometry = function ( editor ) {
+
+	var signals = editor.signals;
 
 
 	var container = new UI.Panel();
 	var container = new UI.Panel();
 
 

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

@@ -2,7 +2,9 @@
  * @author mrdoob / http://mrdoob.com/
  * @author mrdoob / http://mrdoob.com/
  */
  */
 
 
-Sidebar.Geometry.CircleGeometry = function ( signals, object ) {
+Sidebar.Geometry.CircleGeometry = function ( editor, object ) {
+
+	var signals = editor.signals;
 
 
 	var container = new UI.Panel();
 	var container = new UI.Panel();
 
 
@@ -28,20 +30,36 @@ Sidebar.Geometry.CircleGeometry = function ( signals, object ) {
 
 
 	container.add( segmentsRow );
 	container.add( segmentsRow );
 
 
-	//
+	// thetaStart
 
 
-	function update() {
+	var thetaStartRow = new UI.Panel();
+	var thetaStart = new UI.Number( parameters.thetaStart ).onChange( update );
 
 
-		object.geometry.dispose();
+	thetaStartRow.add( new UI.Text( 'Theta start' ).setWidth( '90px' ) );
+	thetaStartRow.add( thetaStart );
 
 
-		object.geometry = new THREE.CircleGeometry(
-			radius.getValue(),
-			segments.getValue()
-		);
+	container.add( thetaStartRow );
+
+	// thetaLength
+
+	var thetaLengthRow = new UI.Panel();
+	var thetaLength = new UI.Number( parameters.thetaLength ).onChange( update );
 
 
-		object.geometry.computeBoundingSphere();
+	thetaLengthRow.add( new UI.Text( 'Theta length' ).setWidth( '90px' ) );
+	thetaLengthRow.add( thetaLength );
 
 
-		signals.geometryChanged.dispatch( object );
+	container.add( thetaLengthRow );
+
+	//
+
+	function update() {
+
+		editor.execute( new SetGeometryCommand( object, new THREE.CircleGeometry(
+			radius.getValue(),
+			segments.getValue(),
+			thetaStart.getValue(),
+			thetaLength.getValue()
+		) ) );
 
 
 	}
 	}
 
 

+ 5 - 9
editor/js/Sidebar.Geometry.CylinderGeometry.js

@@ -2,7 +2,9 @@
  * @author mrdoob / http://mrdoob.com/
  * @author mrdoob / http://mrdoob.com/
  */
  */
 
 
-Sidebar.Geometry.CylinderGeometry = function ( signals, object ) {
+Sidebar.Geometry.CylinderGeometry = function ( editor, object ) {
+
+	var signals = editor.signals;
 
 
 	var container = new UI.Panel();
 	var container = new UI.Panel();
 
 
@@ -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 );
+		) ) );
 
 
 	}
 	}
 
 

+ 3 - 1
editor/js/Sidebar.Geometry.Geometry.js

@@ -2,7 +2,9 @@
  * @author mrdoob / http://mrdoob.com/
  * @author mrdoob / http://mrdoob.com/
  */
  */
 
 
-Sidebar.Geometry.Geometry = function ( signals ) {
+Sidebar.Geometry.Geometry = function ( editor ) {
+
+	var signals = editor.signals;
 
 
 	var container = new UI.Panel();
 	var container = new UI.Panel();
 
 

+ 5 - 7
editor/js/Sidebar.Geometry.IcosahedronGeometry.js

@@ -2,7 +2,9 @@
  * @author mrdoob / http://mrdoob.com/
  * @author mrdoob / http://mrdoob.com/
  */
  */
 
 
-Sidebar.Geometry.IcosahedronGeometry = function ( signals, object ) {
+Sidebar.Geometry.IcosahedronGeometry = function ( editor, object ) {
+
+	var signals = editor.signals;
 
 
 	var container = new UI.Panel();
 	var container = new UI.Panel();
 
 
@@ -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 );
 
 

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

@@ -2,7 +2,9 @@
  * @author mrdoob / http://mrdoob.com/
  * @author mrdoob / http://mrdoob.com/
  */
  */
 
 
-Sidebar.Geometry.Modifiers = function ( signals, object ) {
+Sidebar.Geometry.Modifiers = function ( editor, object ) {
+
+	var signals = editor.signals;
 
 
 	var container = new UI.Panel().setPaddingLeft( '90px' );
 	var container = new UI.Panel().setPaddingLeft( '90px' );
 
 

+ 5 - 9
editor/js/Sidebar.Geometry.PlaneGeometry.js

@@ -2,7 +2,9 @@
  * @author mrdoob / http://mrdoob.com/
  * @author mrdoob / http://mrdoob.com/
  */
  */
 
 
-Sidebar.Geometry.PlaneGeometry = function ( signals, object ) {
+Sidebar.Geometry.PlaneGeometry = function ( editor, object ) {
+
+	var signals = editor.signals;
 
 
 	var container = new UI.Panel();
 	var container = new UI.Panel();
 
 
@@ -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 );
+		) ) );
 
 
 	}
 	}
 
 

+ 5 - 9
editor/js/Sidebar.Geometry.SphereGeometry.js

@@ -2,7 +2,9 @@
  * @author mrdoob / http://mrdoob.com/
  * @author mrdoob / http://mrdoob.com/
  */
  */
 
 
-Sidebar.Geometry.SphereGeometry = function ( signals, object ) {
+Sidebar.Geometry.SphereGeometry = function ( editor, object ) {
+
+	var signals = editor.signals;
 
 
 	var container = new UI.Panel();
 	var container = new UI.Panel();
 
 
@@ -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 );
+		) ) );
 
 
 	}
 	}
 
 

+ 5 - 9
editor/js/Sidebar.Geometry.TorusGeometry.js

@@ -2,7 +2,9 @@
  * @author mrdoob / http://mrdoob.com/
  * @author mrdoob / http://mrdoob.com/
  */
  */
 
 
-Sidebar.Geometry.TorusGeometry = function ( signals, object ) {
+Sidebar.Geometry.TorusGeometry = function ( editor, object ) {
+
+	var signals = editor.signals;
 
 
 	var container = new UI.Panel();
 	var container = new UI.Panel();
 
 
@@ -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 );
+		) ) );
 
 
 	}
 	}
 
 

+ 5 - 9
editor/js/Sidebar.Geometry.TorusKnotGeometry.js

@@ -2,7 +2,9 @@
  * @author mrdoob / http://mrdoob.com/
  * @author mrdoob / http://mrdoob.com/
  */
  */
 
 
-Sidebar.Geometry.TorusKnotGeometry = function ( signals, object ) {
+Sidebar.Geometry.TorusKnotGeometry = function ( editor, object ) {
+
+	var signals = editor.signals;
 
 
 	var container = new UI.Panel();
 	var container = new UI.Panel();
 
 
@@ -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 );
+		) ) );
 
 
 	}
 	}
 
 

+ 19 - 20
editor/js/Sidebar.Geometry.js

@@ -49,10 +49,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 +61,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 +69,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,8 +86,6 @@ Sidebar.Geometry = function ( editor ) {
 
 
 		this.setValue( 'Actions' );
 		this.setValue( 'Actions' );
 
 
-		signals.objectChanged.dispatch( object );
-
 	} );
 	} );
 	container.addStatic( objectActions );
 	container.addStatic( objectActions );
 
 
@@ -100,7 +99,7 @@ Sidebar.Geometry = function ( editor ) {
 
 
 		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() ) );
 
 
 	} );
 	} );
 
 
@@ -115,7 +114,7 @@ Sidebar.Geometry = function ( editor ) {
 	var geometryNameRow = new UI.Panel();
 	var geometryNameRow = new UI.Panel();
 	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,11 +125,11 @@ 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
 
 
@@ -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 ) );
 
 
 			}
 			}
 
 

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

@@ -0,0 +1,136 @@
+/**
+ * @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.CollapsiblePanel();
+	container.setCollapsed( editor.config.getKey( 'ui/sidebar/history/collapsed' ) );
+	container.onCollapsedChange( function ( boolean ) {
+
+		editor.config.setKey( 'ui/sidebar/history/collapsed', boolean );
+
+	} );
+
+	container.addStatic( new UI.Text( 'HISTORY' ) );
+
+	// Checkbox 'Save History'
+
+	var saveHistorySpan = new UI.Span().setPosition( 'absolute' ).setLeft( '180px' ).setFontSize( '13px' );
+	var saveHistoryCheckbox = new UI.Checkbox( config.getKey( 'project/history/stored' ) ).setLeft( '50px' ).onChange( function () {
+
+		config.setKey( 'project/history/stored', this.getValue() );
+		var saveHistory = this.getValue();
+
+		if ( saveHistory ) {
+
+			alert( 'The history will be preserved across a browser refresh.\nThis can have an impact on performance (mainly 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();
+
+		}
+
+	} );
+
+	saveHistorySpan.add( saveHistoryCheckbox );
+	saveHistorySpan.add( new UI.Text( 'Save History' ).setPosition( 'relative' ).setLeft( '5px' ) );
+
+	saveHistorySpan.onClick( function ( event ) {
+
+		event.stopPropagation(); // Avoid panel collapsing
+
+	} );
+
+	container.addStatic( saveHistorySpan );
+
+	container.add( 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;
+
+	} );
+	outliner.onDblClick( function () {
+
+		//editor.focusById( parseInt( outliner.getValue() ) );
+
+	} );
+	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;
+
+};

+ 282 - 57
editor/js/Sidebar.Material.js

@@ -42,7 +42,7 @@ Sidebar.Material = function ( editor ) {
 	var materialNameRow = new UI.Panel();
 	var materialNameRow = new UI.Panel();
 	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() ) );
 
 
 	} );
 	} );
 
 
@@ -60,9 +60,10 @@ Sidebar.Material = function ( editor ) {
 		'LineDashedMaterial': 'LineDashedMaterial',
 		'LineDashedMaterial': 'LineDashedMaterial',
 		'MeshBasicMaterial': 'MeshBasicMaterial',
 		'MeshBasicMaterial': 'MeshBasicMaterial',
 		'MeshDepthMaterial': 'MeshDepthMaterial',
 		'MeshDepthMaterial': 'MeshDepthMaterial',
-		'MeshLambertMaterial': 'MeshLambertMaterial',
 		'MeshNormalMaterial': 'MeshNormalMaterial',
 		'MeshNormalMaterial': 'MeshNormalMaterial',
+		'MeshLambertMaterial': 'MeshLambertMaterial',
 		'MeshPhongMaterial': 'MeshPhongMaterial',
 		'MeshPhongMaterial': 'MeshPhongMaterial',
+		'MeshPhysicalMaterial': 'MeshPhysicalMaterial',
 		'ShaderMaterial': 'ShaderMaterial',
 		'ShaderMaterial': 'ShaderMaterial',
 		'SpriteMaterial': 'SpriteMaterial'
 		'SpriteMaterial': 'SpriteMaterial'
 
 
@@ -82,7 +83,7 @@ Sidebar.Material = function ( editor ) {
 	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 +92,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 +101,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 );
@@ -117,6 +118,26 @@ Sidebar.Material = function ( editor ) {
 
 
 	container.add( materialColorRow );
 	container.add( materialColorRow );
 
 
+	// roughness
+
+	var materialRoughnessRow = new UI.Panel();
+	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.Panel();
+	var materialMetalness = new UI.Number( 1 ).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.Panel();
@@ -237,6 +258,30 @@ Sidebar.Material = function ( editor ) {
 
 
 	container.add( materialDisplacementMapRow );
 	container.add( materialDisplacementMapRow );
 
 
+	// roughness map
+
+	var materialRoughnessMapRow = new UI.Panel();
+	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.Panel();
+	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.Panel();
@@ -343,7 +388,7 @@ Sidebar.Material = function ( editor ) {
 	// opacity
 	// opacity
 
 
 	var materialOpacityRow = new UI.Panel();
 	var materialOpacityRow = new UI.Panel();
-	var materialOpacity = new UI.Number().setWidth( '60px' ).setRange( 0, 1 ).onChange( update );
+	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 );
@@ -400,9 +445,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 +455,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 +464,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 ) {
+
+				editor.execute( new SetMaterialValueCommand( currentObject, 'roughness', materialRoughness.getValue() ) );
+
+			}
+
+			if ( material.metalness !== undefined && Math.abs( material.metalness - materialMetalness.getValue() ) >= 0.01 ) {
 
 
-				material.color.setHex( materialColor.getHexValue() );
+				editor.execute( new SetMaterialValueCommand( currentObject, 'metalness', materialMetalness.getValue() ) );
 
 
 			}
 			}
 
 
-			if ( material.emissive !== undefined ) {
+			if ( material.emissive !== undefined && material.emissive.getHex() !== materialEmissive.getHexValue() ) {
 
 
-				material.emissive.setHex( materialEmissive.getHexValue() );
+				editor.execute( new SetMaterialColorCommand( currentObject, 'emissive', materialEmissive.getHexValue() ) );
 
 
 			}
 			}
 
 
-			if ( material.specular !== undefined ) {
+			if ( material.specular !== undefined && material.specular.getHex() !== materialSpecular.getHexValue() ) {
 
 
-				material.specular.setHex( materialSpecular.getHexValue() );
+				editor.execute( new SetMaterialColorCommand( currentObject, 'specular', materialSpecular.getHexValue() ) );
 
 
 			}
 			}
 
 
-			if ( material.shininess !== undefined ) {
+			if ( material.shininess !== undefined && Math.abs( material.shininess - materialShininess.getValue() ) >= 0.01 ) {
 
 
-				material.shininess = materialShininess.getValue();
+				editor.execute( new SetMaterialValueCommand( currentObject, 'shininess', materialShininess.getValue() ) );
 
 
 			}
 			}
 
 
@@ -449,16 +506,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 +524,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 +545,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 +566,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 +593,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 +614,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 +635,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 +714,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 +736,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 +757,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 {
 
 
@@ -614,49 +780,65 @@ Sidebar.Material = function ( editor ) {
 
 
 			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() ) );
 
 
 			}
 			}
 
 
@@ -681,6 +863,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,6 +876,8 @@ 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,
@@ -718,6 +904,8 @@ Sidebar.Material = function ( editor ) {
 
 
 	function refreshUi( resetTextureSelectors ) {
 	function refreshUi( resetTextureSelectors ) {
 
 
+		if ( !currentObject ) return;
+
 		var material = currentObject.material;
 		var material = currentObject.material;
 
 
 		if ( material.uuid !== undefined ) {
 		if ( material.uuid !== undefined ) {
@@ -740,6 +928,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 +1034,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 );
@@ -959,6 +1183,7 @@ Sidebar.Material = function ( editor ) {
 
 
 	} );
 	} );
 
 
+	signals.materialChanged.add( function () { refreshUi() } );
 	return container;
 	return container;
 
 
 }
 }

+ 118 - 43
editor/js/Sidebar.Object3D.js

@@ -41,23 +41,21 @@ 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 );
 
 
@@ -71,7 +69,7 @@ Sidebar.Object3D = function ( editor ) {
 
 
 		objectUUID.setValue( THREE.Math.generateUUID() );
 		objectUUID.setValue( THREE.Math.generateUUID() );
 
 
-		editor.selected.uuid = objectUUID.getValue();
+		editor.execute( new SetUuidCommand( editor.selected, objectUUID.getValue() ) );
 
 
 	} );
 	} );
 
 
@@ -86,7 +84,7 @@ Sidebar.Object3D = function ( editor ) {
 	var objectNameRow = new UI.Panel();
 	var objectNameRow = new UI.Panel();
 	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() ) );
 
 
 	} );
 	} );
 
 
@@ -95,6 +93,7 @@ Sidebar.Object3D = function ( editor ) {
 
 
 	container.add( objectNameRow );
 	container.add( objectNameRow );
 
 
+	/*
 	// parent
 	// parent
 
 
 	var objectParentRow = new UI.Panel();
 	var objectParentRow = new UI.Panel();
@@ -104,6 +103,7 @@ Sidebar.Object3D = function ( editor ) {
 	objectParentRow.add( objectParent );
 	objectParentRow.add( objectParent );
 
 
 	container.add( objectParentRow );
 	container.add( objectParentRow );
+	*/
 
 
 	// position
 	// position
 
 
@@ -243,6 +243,20 @@ Sidebar.Object3D = function ( editor ) {
 
 
 	container.add( objectDecayRow );
 	container.add( objectDecayRow );
 
 
+	// shadow
+
+	var objectShadowRow = new UI.Panel();
+
+	objectShadowRow.add( new UI.Text( 'Shadow' ).setWidth( '90px' ) );
+
+	var objectCastShadow = new UI.THREE.Boolean( false, 'cast' ).onChange( update );
+	objectShadowRow.add( objectCastShadow );
+
+	var objectReceiveShadow = new UI.THREE.Boolean( false, 'receive' ).onChange( update );
+	objectShadowRow.add( objectReceiveShadow );
+
+	container.add( objectShadowRow );
+
 	// visible
 	// visible
 
 
 	var objectVisibleRow = new UI.Panel();
 	var objectVisibleRow = new UI.Panel();
@@ -342,96 +356,133 @@ Sidebar.Object3D = function ( editor ) {
 
 
 		if ( object !== null ) {
 		if ( object !== null ) {
 
 
+			/*
 			if ( object.parent !== null ) {
 			if ( object.parent !== null ) {
 
 
 				var newParentId = parseInt( objectParent.getValue() );
 				var newParentId = parseInt( objectParent.getValue() );
 
 
 				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 ) );
+
+			}
+
+			var newRotation = new THREE.Euler( objectRotationX.getValue(), objectRotationY.getValue(), objectRotationZ.getValue() );
+			if ( object.rotation.toVector3().distanceTo( newRotation.toVector3() ) >= 0.01 ) {
+
+				editor.execute( new SetRotationCommand( object, newRotation ) );
+
+			}
 
 
-			object.scale.x = objectScaleX.getValue();
-			object.scale.y = objectScaleY.getValue();
-			object.scale.z = objectScaleZ.getValue();
+			var newScale = new THREE.Vector3( objectScaleX.getValue(), objectScaleY.getValue(), objectScaleZ.getValue() );
+			if ( object.scale.distanceTo( newScale ) >= 0.01 ) {
 
 
-			if ( object.fov !== undefined ) {
+				editor.execute( new SetScaleCommand( object, newScale ) );
 
 
-				object.fov = objectFov.getValue();
+			}
+
+			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 ) {
+
+				editor.execute( new SetValueCommand( object, 'near', objectNear.getValue() ) );
+
+			}
+
+			if ( object.far !== undefined && Math.abs( object.far - objectFar.getValue() ) >= 0.01 ) {
+
+				editor.execute( new SetValueCommand( object, 'far', objectFar.getValue() ) );
+
+			}
+
+			if ( object.intensity !== undefined && Math.abs( object.intensity - objectIntensity.getValue() ) >= 0.01 ) {
 
 
-				object.near = objectNear.getValue();
+				editor.execute( new SetValueCommand( object, 'intensity', objectIntensity.getValue() ) );
 
 
 			}
 			}
 
 
-			if ( object.far !== undefined ) {
+			if ( object.color !== undefined && object.color.getHex() !== objectColor.getHexValue() ) {
 
 
-				object.far = objectFar.getValue();
+				editor.execute( new SetColorCommand( object, 'color', objectColor.getHexValue() ) );
 
 
 			}
 			}
 
 
-			if ( object.intensity !== undefined ) {
+			if ( object.groundColor !== undefined && object.groundColor.getHex() !== objectGroundColor.getHexValue() ) {
 
 
-				object.intensity = objectIntensity.getValue();
+				editor.execute( new SetColorCommand( object, 'groundColor', objectGroundColor.getHexValue() ) );
 
 
 			}
 			}
 
 
-			if ( object.color !== undefined ) {
+			if ( object.distance !== undefined && Math.abs( object.distance - objectDistance.getValue() ) >= 0.01 ) {
 
 
-				object.color.setHex( objectColor.getHexValue() );
+				editor.execute( new SetValueCommand( object, 'distance', objectDistance.getValue() ) );
 
 
 			}
 			}
 
 
-			if ( object.groundColor !== undefined ) {
+			if ( object.angle !== undefined && Math.abs( object.angle - objectAngle.getValue() ) >= 0.01 ) {
 
 
-				object.groundColor.setHex( objectGroundColor.getHexValue() );
+				editor.execute( new SetValueCommand( object, 'angle', objectAngle.getValue() ) );
 
 
 			}
 			}
 
 
-			if ( object.distance !== undefined ) {
+			if ( object.exponent !== undefined && Math.abs( object.exponent - objectExponent.getValue() ) >= 0.01 ) {
 
 
-				object.distance = objectDistance.getValue();
+				editor.execute( new SetValueCommand( object, 'exponent', objectExponent.getValue() ) );
 
 
 			}
 			}
 
 
-			if ( object.angle !== undefined ) {
+			if ( object.decay !== undefined && Math.abs( object.decay - objectDecay.getValue() ) >= 0.01 ) {
 
 
-				object.angle = objectAngle.getValue();
+				editor.execute( new SetValueCommand( object, 'decay', objectDecay.getValue() ) );
 
 
 			}
 			}
 
 
-			if ( object.exponent !== undefined ) {
+			if ( object.visible !== objectVisible.getValue() ) {
 
 
-				object.exponent = objectExponent.getValue();
+				editor.execute( new SetValueCommand( object, 'visible', objectVisible.getValue() ) );
 
 
 			}
 			}
 
 
-			if ( object.decay !== undefined ) {
+			if ( object.castShadow !== objectCastShadow.getValue() ) {
 
 
-				object.decay = objectDecay.getValue();
+				editor.execute( new SetValueCommand( object, 'castShadow', objectCastShadow.getValue() ) );
 
 
 			}
 			}
 
 
-			object.visible = objectVisible.getValue();
+			if ( object.receiveShadow !== undefined ) {
+
+				if ( object.receiveShadow !== objectReceiveShadow.getValue() ) {
+
+					editor.execute( new SetValueCommand( object, 'receiveShadow', objectReceiveShadow.getValue() ) );
+					object.material.needsUpdate = true;
+
+				}
+
+			}
 
 
 			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 ) {
 
 
@@ -439,8 +490,6 @@ Sidebar.Object3D = function ( editor ) {
 
 
 			}
 			}
 
 
-			signals.objectChanged.dispatch( object );
-
 		}
 		}
 
 
 	}
 	}
@@ -448,7 +497,7 @@ Sidebar.Object3D = function ( editor ) {
 	function updateRows( object ) {
 	function updateRows( object ) {
 
 
 		var properties = {
 		var properties = {
-			'parent': objectParentRow,
+			// 'parent': objectParentRow,
 			'fov': objectFovRow,
 			'fov': objectFovRow,
 			'near': objectNearRow,
 			'near': objectNearRow,
 			'far': objectFarRow,
 			'far': objectFarRow,
@@ -458,7 +507,9 @@ Sidebar.Object3D = function ( editor ) {
 			'distance' : objectDistanceRow,
 			'distance' : objectDistanceRow,
 			'angle' : objectAngleRow,
 			'angle' : objectAngleRow,
 			'exponent' : objectExponentRow,
 			'exponent' : objectExponentRow,
-			'decay' : objectDecayRow
+			'decay' : objectDecayRow,
+			'castShadow' : objectShadowRow,
+			'receiveShadow' : objectReceiveShadow
 		};
 		};
 
 
 		for ( var property in properties ) {
 		for ( var property in properties ) {
@@ -505,6 +556,7 @@ Sidebar.Object3D = function ( editor ) {
 
 
 	} );
 	} );
 
 
+	/*
 	signals.sceneGraphChanged.add( function () {
 	signals.sceneGraphChanged.add( function () {
 
 
 		var scene = editor.scene;
 		var scene = editor.scene;
@@ -519,6 +571,7 @@ Sidebar.Object3D = function ( editor ) {
 		objectParent.setOptions( options );
 		objectParent.setOptions( options );
 
 
 	} );
 	} );
+	*/
 
 
 	signals.objectChanged.add( function ( object ) {
 	signals.objectChanged.add( function ( object ) {
 
 
@@ -528,6 +581,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 );
@@ -535,11 +596,13 @@ Sidebar.Object3D = function ( editor ) {
 		objectUUID.setValue( object.uuid );
 		objectUUID.setValue( object.uuid );
 		objectName.setValue( object.name );
 		objectName.setValue( object.name );
 
 
+		/*
 		if ( object.parent !== null ) {
 		if ( object.parent !== null ) {
 
 
 			objectParent.setValue( object.parent.id );
 			objectParent.setValue( object.parent.id );
 
 
 		}
 		}
+		*/
 
 
 		objectPositionX.setValue( object.position.x );
 		objectPositionX.setValue( object.position.x );
 		objectPositionY.setValue( object.position.y );
 		objectPositionY.setValue( object.position.y );
@@ -613,6 +676,18 @@ Sidebar.Object3D = function ( editor ) {
 
 
 		}
 		}
 
 
+		if ( object.castShadow !== undefined ) {
+
+			objectCastShadow.setValue( object.castShadow );
+
+		}
+
+		if ( object.receiveShadow !== undefined ) {
+
+			objectReceiveShadow.setValue( object.receiveShadow );
+
+		}
+
 		objectVisible.setValue( object.visible );
 		objectVisible.setValue( object.visible );
 
 
 		try {
 		try {

+ 33 - 16
editor/js/Sidebar.Project.js

@@ -4,6 +4,7 @@
 
 
 Sidebar.Project = function ( editor ) {
 Sidebar.Project = function ( editor ) {
 
 
+	var config = editor.config;
 	var signals = editor.signals;
 	var signals = editor.signals;
 
 
 	var rendererTypes = {
 	var rendererTypes = {
@@ -17,10 +18,10 @@ Sidebar.Project = function ( editor ) {
 	};
 	};
 
 
 	var container = new UI.CollapsiblePanel();
 	var container = new UI.CollapsiblePanel();
-	container.setCollapsed( editor.config.getKey( 'ui/sidebar/project/collapsed' ) );
+	container.setCollapsed( config.getKey( 'ui/sidebar/project/collapsed' ) );
 	container.onCollapsedChange( function ( boolean ) {
 	container.onCollapsedChange( function ( boolean ) {
 
 
-		editor.config.setKey( 'ui/sidebar/project/collapsed', boolean );
+		config.setKey( 'ui/sidebar/project/collapsed', boolean );
 
 
 	} );
 	} );
 
 
@@ -42,7 +43,10 @@ Sidebar.Project = function ( editor ) {
 	var rendererTypeRow = new UI.Panel();
 	var rendererTypeRow = new UI.Panel();
 	var rendererType = new UI.Select().setOptions( options ).setWidth( '150px' ).onChange( function () {
 	var rendererType = new UI.Select().setOptions( options ).setWidth( '150px' ).onChange( function () {
 
 
-		editor.config.setKey( 'project/renderer', this.getValue() );
+		var value = this.getValue();
+
+		config.setKey( 'project/renderer', value );
+
 		updateRenderer();
 		updateRenderer();
 
 
 	} );
 	} );
@@ -52,33 +56,43 @@ Sidebar.Project = function ( editor ) {
 
 
 	container.add( rendererTypeRow );
 	container.add( rendererTypeRow );
 
 
-	if ( editor.config.getKey( 'project/renderer' ) !== undefined ) {
+	if ( config.getKey( 'project/renderer' ) !== undefined ) {
 
 
-		rendererType.setValue( editor.config.getKey( 'project/renderer' ) );
+		rendererType.setValue( config.getKey( 'project/renderer' ) );
 
 
 	}
 	}
 
 
 	// antialiasing
 	// antialiasing
 
 
-	var rendererAntialiasRow = new UI.Panel();
-	var rendererAntialias = new UI.Checkbox( editor.config.getKey( 'project/renderer/antialias' ) ).setLeft( '100px' ).onChange( function () {
+	var rendererPropertiesRow = new UI.Panel();
+	rendererPropertiesRow.add( new UI.Text( '' ).setWidth( '90px' ) );
+
+	var rendererAntialias = new UI.THREE.Boolean( config.getKey( 'project/renderer/antialias' ), 'antialias' ).onChange( function () {
 
 
-		editor.config.setKey( 'project/renderer/antialias', this.getValue() );
+		config.setKey( 'project/renderer/antialias', this.getValue() );
 		updateRenderer();
 		updateRenderer();
 
 
 	} );
 	} );
+	rendererPropertiesRow.add( rendererAntialias );
 
 
-	rendererAntialiasRow.add( new UI.Text( 'Antialias' ).setWidth( '90px' ) );
-	rendererAntialiasRow.add( rendererAntialias );
+	// shadow
 
 
-	container.add( rendererAntialiasRow );
+	var rendererShadows = new UI.THREE.Boolean( config.getKey( 'project/renderer/shadows' ), 'shadows' ).onChange( function () {
+
+		config.setKey( 'project/renderer/shadows', this.getValue() );
+		updateRenderer();
+
+	} );
+	rendererPropertiesRow.add( rendererShadows );
+
+	container.add( rendererPropertiesRow );
 
 
 	// VR
 	// VR
 
 
 	var vrRow = new UI.Panel();
 	var vrRow = new UI.Panel();
-	var vr = new UI.Checkbox( editor.config.getKey( 'project/vr' ) ).setLeft( '100px' ).onChange( function () {
+	var vr = new UI.Checkbox( config.getKey( 'project/vr' ) ).setLeft( '100px' ).onChange( function () {
 
 
-		editor.config.setKey( 'project/vr', this.getValue() );
+		config.setKey( 'project/vr', this.getValue() );
 		// updateRenderer();
 		// updateRenderer();
 
 
 	} );
 	} );
@@ -92,11 +106,11 @@ Sidebar.Project = function ( editor ) {
 
 
 	function updateRenderer() {
 	function updateRenderer() {
 
 
-		createRenderer( rendererType.getValue(), rendererAntialias.getValue() );
+		createRenderer( rendererType.getValue(), rendererAntialias.getValue(), rendererShadows.getValue() );
 
 
 	}
 	}
 
 
-	function createRenderer( type, antialias ) {
+	function createRenderer( type, antialias, shadows ) {
 
 
 		if ( type === 'WebGLRenderer' && System.support.webgl === false ) {
 		if ( type === 'WebGLRenderer' && System.support.webgl === false ) {
 
 
@@ -104,12 +118,15 @@ 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;
 		signals.rendererChanged.dispatch( renderer );
 		signals.rendererChanged.dispatch( renderer );
 
 
 	}
 	}
 
 
-	createRenderer( editor.config.getKey( 'project/renderer' ), editor.config.getKey( 'project/renderer/antialias' ) );
+	createRenderer( config.getKey( 'project/renderer' ), config.getKey( 'project/renderer/antialias' ), config.getKey( 'project/renderer/shadows' ) );
 
 
 	return container;
 	return container;
 
 

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

@@ -27,7 +27,7 @@ Sidebar.Script = function ( editor ) {
 	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;
 
 

+ 1 - 0
editor/js/Sidebar.js

@@ -8,6 +8,7 @@ var Sidebar = function ( editor ) {
 	container.setId( 'sidebar' );
 	container.setId( 'sidebar' );
 
 
 	container.add( new Sidebar.Project( editor ) );
 	container.add( new Sidebar.Project( editor ) );
+	container.add( new Sidebar.History( editor ) );
 	container.add( new Sidebar.Scene( editor ) );
 	container.add( new Sidebar.Scene( editor ) );
 	container.add( new Sidebar.Object3D( editor ) );
 	container.add( new Sidebar.Object3D( editor ) );
 	container.add( new Sidebar.Geometry( editor ) );
 	container.add( new Sidebar.Geometry( editor ) );

+ 2 - 2
editor/js/Storage.js

@@ -76,14 +76,14 @@ var Storage = function () {
 
 
 		},
 		},
 
 
-		clear: function ( callback ) {
+		clear: function () {
 
 
 			var transaction = database.transaction( [ 'states' ], 'readwrite' );
 			var transaction = database.transaction( [ 'states' ], 'readwrite' );
 			var objectStore = transaction.objectStore( 'states' );
 			var objectStore = transaction.objectStore( 'states' );
 			var request = objectStore.clear();
 			var request = objectStore.clear();
 			request.onsuccess = function ( event ) {
 			request.onsuccess = function ( event ) {
 
 
-				callback();
+				console.log( '[' + /\d\d\:\d\d\:\d\d/.exec( new Date() )[ 0 ] + ']', 'Cleared IndexedDB.' );
 
 
 			};
 			};
 
 

+ 5 - 9
editor/js/Toolbar.js

@@ -37,22 +37,18 @@ var Toolbar = function ( editor ) {
 
 
 	// grid
 	// grid
 
 
-	var grid = new UI.Number( 25 ).onChange( update );
-	grid.dom.style.width = '42px';
-	buttons.add( new UI.Text( 'Grid: ' ) );
+	var grid = new UI.Number( 25 ).setWidth( '40px' ).onChange( update );
+	buttons.add( new UI.Text( 'grid: ' ) );
 	buttons.add( grid );
 	buttons.add( grid );
 
 
-	var snap = new UI.Checkbox( false ).onChange( update );
+	var snap = new UI.THREE.Boolean( false, 'snap' ).onChange( update );
 	buttons.add( snap );
 	buttons.add( snap );
-	buttons.add( new UI.Text( 'snap' ) );
 
 
-	var local = new UI.Checkbox( false ).onChange( update );
+	var local = new UI.THREE.Boolean( false, 'local' ).onChange( update );
 	buttons.add( local );
 	buttons.add( local );
-	buttons.add( new UI.Text( 'local' ) );
 
 
-	var showGrid = new UI.Checkbox().onChange( update ).setValue( true );
+	var showGrid = new UI.THREE.Boolean( true, 'show' ).onChange( update );
 	buttons.add( showGrid );
 	buttons.add( showGrid );
-	buttons.add( new UI.Text( 'show' ) );
 
 
 	function update() {
 	function update() {
 
 

+ 43 - 57
editor/js/Viewport.js

@@ -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,36 @@ 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)) {
 
 
-			( function ( matrix1, matrix2 ) {
+						editor.execute(new SetRotationCommand( object, object.rotation, objectRotationOnDown ));
 
 
-				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 );
 					}
 					}
-				);
+					break;
+				case 'scale':
+					if (!objectScaleOnDown.equals(object.scale)) {
 
 
-			} )( matrix.clone(), object.matrix.clone() );
+						editor.execute(new SetScaleCommand( object, object.scale, objectScaleOnDown ));
+
+					}
+					break;
+
+			}
 
 
 		}
 		}
 
 
-		signals.objectChanged.dispatch( object );
 		controls.enabled = true;
 		controls.enabled = true;
 
 
 	} );
 	} );
@@ -358,9 +374,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 +388,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 +423,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 +467,6 @@ var Viewport = function ( editor ) {
 
 
 			}
 			}
 
 
-			updateMaterials();
-
 			oldFogType = fogType;
 			oldFogType = fogType;
 
 
 		}
 		}
@@ -513,30 +523,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 ) {

+ 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 );
+
+		}
+
+	}
+
+};

+ 76 - 0
editor/js/commands/AddScriptCommand.js

@@ -0,0 +1,76 @@
+/**
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ */
+
+/**
+ * @param object THREE.Object3D
+ * @param script javascript object
+ * @constructor
+ */
+
+var AddScriptCommand = function ( object, script ) {
+
+	Command.call( this );
+
+	this.type = 'AddScriptCommand';
+	this.name = 'Add Script';
+
+	this.object = object;
+	this.script = script;
+
+};
+
+AddScriptCommand.prototype = {
+
+	execute: function () {
+
+		if ( this.editor.scripts[ this.object.uuid ] === undefined ) {
+
+			this.editor.scripts[ this.object.uuid ] = [];
+
+		}
+
+		this.editor.scripts[ this.object.uuid ].push( this.script );
+
+		this.editor.signals.scriptAdded.dispatch( this.script );
+
+	},
+
+	undo: function () {
+
+		if ( this.editor.scripts[ this.object.uuid ] === undefined ) return;
+
+		var index = this.editor.scripts[ this.object.uuid ].indexOf( this.script );
+
+		if ( index !== - 1 ) {
+
+			this.editor.scripts[ this.object.uuid ].splice( index, 1 );
+
+		}
+
+		this.editor.signals.scriptRemoved.dispatch( this.script );
+
+	},
+
+	toJSON: function () {
+
+		var output = Command.prototype.toJSON.call( this );
+
+		output.objectUuid = this.object.uuid;
+		output.script = this.script;
+
+		return output;
+
+	},
+
+	fromJSON: function ( json ) {
+
+		Command.prototype.fromJSON.call( this, json );
+
+		this.script = json.script;
+		this.object = this.editor.objectByUuid( json.objectUuid );
+
+	}
+
+};

+ 107 - 0
editor/js/commands/MoveObjectCommand.js

@@ -0,0 +1,107 @@
+/**
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ */
+
+/**
+ * @param object THREE.Object3D
+ * @param newParent THREE.Object3D
+ * @param newBefore THREE.Object3D
+ * @constructor
+ */
+
+var MoveObjectCommand = function ( object, newParent, newBefore ) {
+
+	Command.call( this );
+
+	this.type = 'MoveObjectCommand';
+	this.name = 'Move Object';
+
+	this.object = object;
+	this.oldParent = ( object !== undefined ) ? object.parent : undefined;
+	this.oldIndex = ( this.oldParent !== undefined ) ? this.oldParent.children.indexOf( this.object ) : undefined;
+	this.newParent = newParent;
+
+	if ( newBefore !== undefined ) {
+
+		this.newIndex = ( newParent !== undefined ) ? newParent.children.indexOf( newBefore ) : undefined;
+
+	} else {
+
+		this.newIndex = ( newParent !== undefined ) ? newParent.children.length : undefined;
+
+	}
+
+	if ( this.oldParent === this.newParent && this.newIndex > this.oldIndex ) {
+
+		this.newIndex --;
+
+	}
+
+	this.newBefore = newBefore;
+
+};
+
+MoveObjectCommand.prototype = {
+
+	execute: function () {
+
+		this.oldParent.remove( this.object );
+
+		var children = this.newParent.children;
+		children.splice( this.newIndex, 0, this.object );
+		this.object.parent = this.newParent;
+
+		this.editor.signals.sceneGraphChanged.dispatch();
+
+	},
+
+	undo: function () {
+
+		this.newParent.remove( this.object );
+
+		var children = this.oldParent.children;
+		children.splice( this.oldIndex, 0, this.object );
+		this.object.parent = this.oldParent;
+
+		this.editor.signals.sceneGraphChanged.dispatch();
+
+	},
+
+	toJSON: function () {
+
+		var output = Command.prototype.toJSON.call( this );
+
+		output.objectUuid = this.object.uuid;
+		output.newParentUuid = this.newParent.uuid;
+		output.oldParentUuid = this.oldParent.uuid;
+		output.newIndex = this.newIndex;
+		output.oldIndex = this.oldIndex;
+
+		return output;
+
+	},
+
+	fromJSON: function ( json ) {
+
+		Command.prototype.fromJSON.call( this, json );
+
+		this.object = this.editor.objectByUuid( json.objectUuid );
+		this.oldParent = this.editor.objectByUuid( json.oldParentUuid );
+		if ( this.oldParent === undefined ) {
+
+			this.oldParent = this.editor.scene;
+
+		}
+		this.newParent = this.editor.objectByUuid( json.newParentUuid );
+		if ( this.newParent === undefined ) {
+
+			this.newParent = this.editor.scene;
+
+		}
+		this.newIndex = json.newIndex;
+		this.oldIndex = json.oldIndex;
+
+	}
+
+};

+ 85 - 0
editor/js/commands/MultiCmdsCommand.js

@@ -0,0 +1,85 @@
+/**
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ */
+
+/**
+ * @param cmdArray array containing command objects
+ * @constructor
+ */
+
+var MultiCmdsCommand = function ( cmdArray ) {
+
+	Command.call( this );
+
+	this.type = 'MultiCmdsCommand';
+	this.name = 'Multiple Changes';
+
+	this.cmdArray = ( cmdArray !== undefined ) ? cmdArray : [];
+
+};
+
+MultiCmdsCommand.prototype = {
+
+	execute: function () {
+
+		this.editor.signals.sceneGraphChanged.active = false;
+
+		for ( var i = 0; i < this.cmdArray.length; i ++ ) {
+
+			this.cmdArray[ i ].execute();
+
+		}
+
+		this.editor.signals.sceneGraphChanged.active = true;
+		this.editor.signals.sceneGraphChanged.dispatch();
+
+	},
+
+	undo: function () {
+
+		this.editor.signals.sceneGraphChanged.active = false;
+
+		for ( var i = this.cmdArray.length - 1; i >= 0; i -- ) {
+
+			this.cmdArray[ i ].undo();
+
+		}
+
+		this.editor.signals.sceneGraphChanged.active = true;
+		this.editor.signals.sceneGraphChanged.dispatch();
+
+	},
+
+	toJSON: function () {
+
+		var output = Command.prototype.toJSON.call( this );
+
+		var cmds = [];
+		for ( var i = 0; i < this.cmdArray.length; i ++ ) {
+
+			cmds.push( this.cmdArray[ i ].toJSON() );
+
+		}
+		output.cmds = cmds;
+
+		return output;
+
+	},
+
+	fromJSON: function ( json ) {
+
+		Command.prototype.fromJSON.call( this, json );
+
+		var cmds = json.cmds;
+		for ( var i = 0; i < cmds.length; i ++ ) {
+
+			var cmd = new window[ cmds[ i ].type ]();	// creates a new object of type "json.type"
+			cmd.fromJSON( cmds[ i ] );
+			this.cmdArray.push( cmd );
+
+		}
+
+	}
+
+};

+ 103 - 0
editor/js/commands/RemoveObjectCommand.js

@@ -0,0 +1,103 @@
+/**
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ */
+
+/**
+ * @param object THREE.Object3D
+ * @constructor
+ */
+
+var RemoveObjectCommand = function ( object ) {
+
+	Command.call( this );
+
+	this.type = 'RemoveObjectCommand';
+	this.name = 'Remove Object';
+
+	this.object = object;
+	this.parent = ( object !== undefined ) ? object.parent : undefined;
+	if ( this.parent !== undefined ) {
+
+		this.index = this.parent.children.indexOf( this.object );
+
+	}
+
+};
+
+RemoveObjectCommand.prototype = {
+
+	execute: function () {
+
+		var scope = this.editor;
+		this.object.traverse( function ( child ) {
+
+			scope.removeHelper( child );
+
+		} );
+
+		this.parent.remove( this.object );
+		this.editor.select( this.parent );
+
+		this.editor.signals.objectRemoved.dispatch( this.object );
+		this.editor.signals.sceneGraphChanged.dispatch();
+
+	},
+
+	undo: function () {
+
+		var scope = this.editor;
+
+		this.object.traverse( function ( child ) {
+
+			if ( child.geometry !== undefined ) scope.addGeometry( child.geometry );
+			if ( child.material !== undefined ) scope.addMaterial( child.material );
+
+			scope.addHelper( child );
+
+		} );
+
+		this.parent.children.splice( this.index, 0, this.object );
+		this.object.parent = this.parent;
+		this.editor.select( this.object );
+
+		this.editor.signals.objectAdded.dispatch( this.object );
+		this.editor.signals.sceneGraphChanged.dispatch();
+
+	},
+
+	toJSON: function () {
+
+		var output = Command.prototype.toJSON.call( this );
+		output.object = this.object.toJSON();
+		output.index = this.index;
+		output.parentUuid = this.parent.uuid;
+
+		return output;
+
+	},
+
+	fromJSON: function ( json ) {
+
+		Command.prototype.fromJSON.call( this, json );
+
+		this.parent = this.editor.objectByUuid( json.parentUuid );
+		if ( this.parent === undefined ) {
+
+			this.parent = this.editor.scene;
+
+		}
+
+		this.index = json.index;
+
+		this.object = this.editor.objectByUuid( json.object.object.uuid );
+		if ( this.object === undefined ) {
+
+			var loader = new THREE.ObjectLoader();
+			this.object = loader.parse( json.object );
+
+		}
+
+	}
+
+};

+ 81 - 0
editor/js/commands/RemoveScriptCommand.js

@@ -0,0 +1,81 @@
+/**
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ */
+
+/**
+ * @param object THREE.Object3D
+ * @param script javascript object
+ * @constructor
+ */
+
+var RemoveScriptCommand = function ( object, script ) {
+
+	Command.call( this );
+
+	this.type = 'RemoveScriptCommand';
+	this.name = 'Remove Script';
+
+	this.object = object;
+	this.script = script;
+	if ( this.object && this.script ) {
+
+		this.index = this.editor.scripts[ this.object.uuid ].indexOf( this.script );
+
+	}
+
+};
+
+RemoveScriptCommand.prototype = {
+
+	execute: function () {
+
+		if ( this.editor.scripts[ this.object.uuid ] === undefined ) return;
+
+		if ( this.index !== - 1 ) {
+
+			this.editor.scripts[ this.object.uuid ].splice( this.index, 1 );
+
+		}
+
+		this.editor.signals.scriptRemoved.dispatch( this.script );
+
+	},
+
+	undo: function () {
+
+		if ( this.editor.scripts[ this.object.uuid ] === undefined ) {
+
+			this.editor.scripts[ this.object.uuid ] = [];
+
+		}
+
+		this.editor.scripts[ this.object.uuid ].splice( this.index, 0, this.script );
+
+		this.editor.signals.scriptAdded.dispatch( this.script );
+
+	},
+
+	toJSON: function () {
+
+		var output = Command.prototype.toJSON.call( this );
+
+		output.objectUuid = this.object.uuid;
+		output.script = this.script;
+		output.index = this.index;
+
+		return output;
+
+	},
+
+	fromJSON: function ( json ) {
+
+		Command.prototype.fromJSON.call( this, json );
+
+		this.script = json.script;
+		this.index = json.index;
+		this.object = this.editor.objectByUuid( json.objectUuid );
+
+	}
+
+};

+ 74 - 0
editor/js/commands/SetColorCommand.js

@@ -0,0 +1,74 @@
+/**
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ */
+
+/**
+ * @param object THREE.Object3D
+ * @param attributeName string
+ * @param newValue integer representing a hex color value
+ * @constructor
+ */
+
+var SetColorCommand = function ( object, attributeName, newValue ) {
+
+	Command.call( this );
+
+	this.type = 'SetColorCommand';
+	this.name = 'Set ' + attributeName;
+	this.updatable = true;
+
+	this.object = object;
+	this.attributeName = attributeName;
+	this.oldValue = ( object !== undefined ) ? this.object[ this.attributeName ].getHex() : undefined;
+	this.newValue = newValue;
+
+};
+
+SetColorCommand.prototype = {
+
+	execute: function () {
+
+		this.object[ this.attributeName ].setHex( this.newValue );
+		this.editor.signals.objectChanged.dispatch( this.object );
+
+	},
+
+	undo: function () {
+
+		this.object[ this.attributeName ].setHex( this.oldValue );
+		this.editor.signals.objectChanged.dispatch( this.object );
+
+	},
+
+	update: function ( cmd ) {
+
+		this.newValue = cmd.newValue;
+
+	},
+
+	toJSON: function () {
+
+		var output = Command.prototype.toJSON.call( this );
+
+		output.objectUuid = this.object.uuid;
+		output.attributeName = this.attributeName;
+		output.oldValue = this.oldValue;
+		output.newValue = this.newValue;
+
+		return output;
+
+	},
+
+	fromJSON: function ( json ) {
+
+		Command.prototype.fromJSON.call( this, json );
+
+		this.object = this.editor.objectByUuid( json.objectUuid );
+		this.attributeName = json.attributeName;
+		this.oldValue = json.oldValue;
+		this.newValue = json.newValue;
+
+	}
+
+};

+ 86 - 0
editor/js/commands/SetGeometryCommand.js

@@ -0,0 +1,86 @@
+/**
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ */
+
+/**
+ * @param object THREE.Object3D
+ * @param newGeometry THREE.Geometry
+ * @constructor
+ */
+
+var SetGeometryCommand = function ( object, newGeometry ) {
+
+	Command.call( this );
+
+	this.type = 'SetGeometryCommand';
+	this.name = 'Set Geometry';
+	this.updatable = true;
+
+	this.object = object;
+	this.oldGeometry = ( object !== undefined ) ? object.geometry : undefined;
+	this.newGeometry = newGeometry;
+
+};
+
+SetGeometryCommand.prototype = {
+
+	execute: function () {
+
+		this.object.geometry.dispose();
+		this.object.geometry = this.newGeometry;
+		this.object.geometry.computeBoundingSphere();
+
+		this.editor.signals.geometryChanged.dispatch( this.object );
+		this.editor.signals.sceneGraphChanged.dispatch();
+
+	},
+
+	undo: function () {
+
+		this.object.geometry.dispose();
+		this.object.geometry = this.oldGeometry;
+		this.object.geometry.computeBoundingSphere();
+
+		this.editor.signals.geometryChanged.dispatch( this.object );
+		this.editor.signals.sceneGraphChanged.dispatch();
+
+	},
+
+	update: function ( cmd ) {
+
+		this.newGeometry = cmd.newGeometry;
+
+	},
+
+	toJSON: function () {
+
+		var output = Command.prototype.toJSON.call( this );
+
+		output.objectUuid = this.object.uuid;
+		output.oldGeometry = this.object.geometry.toJSON();
+		output.newGeometry = this.newGeometry.toJSON();
+
+		return output;
+
+	},
+
+	fromJSON: function ( json ) {
+
+		Command.prototype.fromJSON.call( this, json );
+
+		this.object = this.editor.objectByUuid( json.objectUuid );
+
+		this.oldGeometry = parseGeometry( json.oldGeometry );
+		this.newGeometry = parseGeometry( json.newGeometry );
+
+		function parseGeometry ( data ) {
+
+			var loader = new THREE.ObjectLoader();
+			return loader.parseGeometries( [ data ] )[ data.uuid ];
+
+		}
+
+	}
+
+};

+ 71 - 0
editor/js/commands/SetGeometryValueCommand.js

@@ -0,0 +1,71 @@
+/**
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ */
+
+/**
+ * @param object THREE.Object3D
+ * @param attributeName string
+ * @param newValue number, string, boolean or object
+ * @constructor
+ */
+
+var SetGeometryValueCommand = function ( object, attributeName, newValue ) {
+
+	Command.call( this );
+
+	this.type = 'SetGeometryValueCommand';
+	this.name = 'Set Geometry.' + attributeName;
+
+	this.object = object;
+	this.attributeName = attributeName;
+	this.oldValue = ( object !== undefined ) ? object.geometry[ attributeName ] : undefined;
+	this.newValue = newValue;
+
+};
+
+SetGeometryValueCommand.prototype = {
+
+	execute: function () {
+
+		this.object.geometry[ this.attributeName ] = this.newValue;
+		this.editor.signals.objectChanged.dispatch( this.object );
+		this.editor.signals.geometryChanged.dispatch();
+		this.editor.signals.sceneGraphChanged.dispatch();
+
+	},
+
+	undo: function () {
+
+		this.object.geometry[ this.attributeName ] = this.oldValue;
+		this.editor.signals.objectChanged.dispatch( this.object );
+		this.editor.signals.geometryChanged.dispatch();
+		this.editor.signals.sceneGraphChanged.dispatch();
+
+	},
+
+	toJSON: function () {
+
+		var output = Command.prototype.toJSON.call( this );
+
+		output.objectUuid = this.object.uuid;
+		output.attributeName = this.attributeName;
+		output.oldValue = this.oldValue;
+		output.newValue = this.newValue;
+
+		return output;
+
+	},
+
+	fromJSON: function ( json ) {
+
+		Command.prototype.fromJSON.call( this, json );
+
+		this.object = this.editor.objectByUuid( json.objectUuid );
+		this.attributeName = json.attributeName;
+		this.oldValue = json.oldValue;
+		this.newValue = json.newValue;
+
+	}
+
+};

+ 74 - 0
editor/js/commands/SetMaterialColorCommand.js

@@ -0,0 +1,74 @@
+/**
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ */
+
+/**
+ * @param object THREE.Object3D
+ * @param attributeName string
+ * @param newValue integer representing a hex color value
+ * @constructor
+ */
+
+var SetMaterialColorCommand = function ( object, attributeName, newValue ) {
+
+	Command.call( this );
+
+	this.type = 'SetMaterialColorCommand';
+	this.name = 'Set Material.' + attributeName;
+	this.updatable = true;
+
+	this.object = object;
+	this.attributeName = attributeName;
+	this.oldValue = ( object !== undefined ) ? this.object.material[ this.attributeName ].getHex() : undefined;
+	this.newValue = newValue;
+
+};
+
+SetMaterialColorCommand.prototype = {
+
+	execute: function () {
+
+		this.object.material[ this.attributeName ].setHex( this.newValue );
+		this.editor.signals.materialChanged.dispatch( this.object.material );
+
+	},
+
+	undo: function () {
+
+		this.object.material[ this.attributeName ].setHex( this.oldValue );
+		this.editor.signals.materialChanged.dispatch( this.object.material );
+
+	},
+
+	update: function ( cmd ) {
+
+		this.newValue = cmd.newValue;
+
+	},
+
+	toJSON: function () {
+
+		var output = Command.prototype.toJSON.call( this );
+
+		output.objectUuid = this.object.uuid;
+		output.attributeName = this.attributeName;
+		output.oldValue = this.oldValue;
+		output.newValue = this.newValue;
+
+		return output;
+
+	},
+
+	fromJSON: function ( json ) {
+
+		Command.prototype.fromJSON.call( this, json );
+
+		this.object = this.editor.objectByUuid( json.objectUuid );
+		this.attributeName = json.attributeName;
+		this.oldValue = json.oldValue;
+		this.newValue = json.newValue;
+
+	}
+
+};

+ 74 - 0
editor/js/commands/SetMaterialCommand.js

@@ -0,0 +1,74 @@
+/**
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ */
+
+/**
+ * @param object THREE.Object3D
+ * @param newMaterial THREE.Material
+ * @constructor
+ */
+
+var SetMaterialCommand = function ( object, newMaterial ) {
+
+	Command.call( this );
+
+	this.type = 'SetMaterialCommand';
+	this.name = 'New Material';
+
+	this.object = object;
+	this.oldMaterial = ( object !== undefined ) ? object.material : undefined;
+	this.newMaterial = newMaterial;
+
+};
+
+SetMaterialCommand.prototype = {
+
+	execute: function () {
+
+		this.object.material = this.newMaterial;
+		this.editor.signals.materialChanged.dispatch( this.newMaterial );
+
+	},
+
+	undo: function () {
+
+		this.object.material = this.oldMaterial;
+		this.editor.signals.materialChanged.dispatch( this.oldMaterial );
+
+	},
+
+	toJSON: function () {
+
+		var output = Command.prototype.toJSON.call( this );
+
+		output.objectUuid = this.object.uuid;
+		output.oldMaterial = this.oldMaterial.toJSON();
+		output.newMaterial = this.newMaterial.toJSON();
+
+		return output;
+
+	},
+
+	fromJSON: function ( json ) {
+
+		Command.prototype.fromJSON.call( this, json );
+
+		this.object = this.editor.objectByUuid( json.objectUuid );
+		this.oldMaterial = parseMaterial( json.oldMaterial );
+		this.newMaterial = parseMaterial( json.newMaterial );
+
+
+		function parseMaterial ( json ) {
+
+			var loader = new THREE.ObjectLoader();
+			var images = loader.parseImages( json.images );
+			var textures  = loader.parseTextures( json.textures, images );
+			var materials = loader.parseMaterials( [ json ], textures );
+			return materials[ json.uuid ];
+
+		}
+
+	}
+
+};

+ 125 - 0
editor/js/commands/SetMaterialMapCommand.js

@@ -0,0 +1,125 @@
+/**
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ */
+
+/**
+ * @param object THREE.Object3D
+ * @param mapName string
+ * @param newMap THREE.Texture
+ * @constructor
+ */
+
+var SetMaterialMapCommand = function ( object, mapName, newMap ) {
+
+	Command.call( this );
+	this.type = 'SetMaterialMapCommand';
+	this.name = 'Set Material.' + mapName;
+
+	this.object = object;
+	this.mapName = mapName;
+	this.oldMap = ( object !== undefined ) ? object.material[ mapName ] : undefined;
+	this.newMap = newMap;
+
+};
+
+SetMaterialMapCommand.prototype = {
+
+	execute: function () {
+
+		this.object.material[ this.mapName ] = this.newMap;
+		this.object.material.needsUpdate = true;
+		this.editor.signals.materialChanged.dispatch( this.object.material );
+
+	},
+
+	undo: function () {
+
+		this.object.material[ this.mapName ] = this.oldMap;
+		this.object.material.needsUpdate = true;
+		this.editor.signals.materialChanged.dispatch( this.object.material );
+
+	},
+
+	toJSON: function () {
+
+		var output = Command.prototype.toJSON.call( this );
+
+		output.objectUuid = this.object.uuid;
+		output.mapName = this.mapName;
+		output.newMap = serializeMap( this.newMap );
+		output.oldMap = serializeMap( this.oldMap );
+
+		return output;
+
+		// serializes a map (THREE.Texture)
+
+		function serializeMap ( map ) {
+
+			if ( map === null || map === undefined ) return null;
+
+			var meta = {
+				geometries: {},
+				materials: {},
+				textures: {},
+				images: {}
+			};
+
+			var json = map.toJSON( meta );
+			var images = extractFromCache( meta.images );
+			if ( images.length > 0 ) json.images = images;
+			json.sourceFile = map.sourceFile;
+
+			return json;
+
+		}
+
+		// Note: The function 'extractFromCache' is copied from Object3D.toJSON()
+
+		// extract data from the cache hash
+		// remove metadata on each item
+		// and return as array
+		function extractFromCache ( cache ) {
+
+			var values = [];
+			for ( var key in cache ) {
+
+				var data = cache[ key ];
+				delete data.metadata;
+				values.push( data );
+
+			}
+			return values;
+
+		}
+
+	},
+
+	fromJSON: function ( json ) {
+
+		Command.prototype.fromJSON.call( this, json );
+
+		this.object = this.editor.objectByUuid( json.objectUuid );
+		this.mapName = json.mapName;
+		this.oldMap = parseTexture( json.oldMap );
+		this.newMap = parseTexture( json.newMap );
+
+		function parseTexture ( json ) {
+
+			var map = null;
+			if ( json !== null ) {
+
+				var loader = new THREE.ObjectLoader();
+				var images = loader.parseImages( json.images );
+				var textures  = loader.parseTextures( [ json ], images );
+				map = textures[ json.uuid ];
+				map.sourceFile = json.sourceFile;
+
+			}
+			return map;
+
+		}
+
+	}
+
+};

+ 76 - 0
editor/js/commands/SetMaterialValueCommand.js

@@ -0,0 +1,76 @@
+/**
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ */
+
+/**
+ * @param object THREE.Object3D
+ * @param attributeName string
+ * @param newValue number, string, boolean or object
+ * @constructor
+ */
+
+var SetMaterialValueCommand = function ( object, attributeName, newValue ) {
+
+	Command.call( this );
+
+	this.type = 'SetMaterialValueCommand';
+	this.name = 'Set Material.' + attributeName;
+	this.updatable = true;
+
+	this.object = object;
+	this.oldValue = ( object !== undefined ) ? object.material[ attributeName ] : undefined;
+	this.newValue = newValue;
+	this.attributeName = attributeName;
+
+};
+
+SetMaterialValueCommand.prototype = {
+
+	execute: function () {
+
+		this.object.material[ this.attributeName ] = this.newValue;
+		this.object.material.needsUpdate = true;
+		this.editor.signals.materialChanged.dispatch( this.object.material );
+
+	},
+
+	undo: function () {
+
+		this.object.material[ this.attributeName ] = this.oldValue;
+		this.object.material.needsUpdate = true;
+		this.editor.signals.materialChanged.dispatch( this.object.material );
+
+	},
+
+	update: function ( cmd ) {
+
+		this.newValue = cmd.newValue;
+
+	},
+
+	toJSON: function () {
+
+		var output = Command.prototype.toJSON.call( this );
+
+		output.objectUuid = this.object.uuid;
+		output.attributeName = this.attributeName;
+		output.oldValue = this.oldValue;
+		output.newValue = this.newValue;
+
+		return output;
+
+	},
+
+	fromJSON: function ( json ) {
+
+		Command.prototype.fromJSON.call( this, json );
+
+		this.attributeName = json.attributeName;
+		this.oldValue = json.oldValue;
+		this.newValue = json.newValue;
+		this.object = this.editor.objectByUuid( json.objectUuid );
+
+	}
+
+};

+ 83 - 0
editor/js/commands/SetPositionCommand.js

@@ -0,0 +1,83 @@
+/**
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ */
+
+/**
+ * @param object THREE.Object3D
+ * @param newPosition THREE.Vector3
+ * @param optionalOldPosition THREE.Vector3
+ * @constructor
+ */
+
+var SetPositionCommand = function ( object, newPosition, optionalOldPosition ) {
+
+	Command.call( this );
+
+	this.type = 'SetPositionCommand';
+	this.name = 'Set Position';
+	this.updatable = true;
+
+	this.object = object;
+
+	if ( object !== undefined && newPosition !== undefined ) {
+
+		this.oldPosition = object.position.clone();
+		this.newPosition = newPosition.clone();
+
+	}
+
+	if ( optionalOldPosition !== undefined ) {
+
+		this.oldPosition = optionalOldPosition.clone();
+
+	}
+
+};
+SetPositionCommand.prototype = {
+
+	execute: function () {
+
+		this.object.position.copy( this.newPosition );
+		this.object.updateMatrixWorld( true );
+		this.editor.signals.objectChanged.dispatch( this.object );
+
+	},
+
+	undo: function () {
+
+		this.object.position.copy( this.oldPosition );
+		this.object.updateMatrixWorld( true );
+		this.editor.signals.objectChanged.dispatch( this.object );
+
+	},
+
+	update: function ( command ) {
+
+		this.newPosition.copy( command.newPosition );
+
+	},
+
+	toJSON: function () {
+
+		var output = Command.prototype.toJSON.call( this );
+
+		output.objectUuid = this.object.uuid;
+		output.oldPosition = this.oldPosition.toArray();
+		output.newPosition = this.newPosition.toArray();
+
+		return output;
+
+	},
+
+	fromJSON: function ( json ) {
+
+		Command.prototype.fromJSON.call( this, json );
+
+		this.object = this.editor.objectByUuid( json.objectUuid );
+		this.oldPosition = new THREE.Vector3().fromArray( json.oldPosition );
+		this.newPosition = new THREE.Vector3().fromArray( json.newPosition );
+
+	}
+
+};

+ 84 - 0
editor/js/commands/SetRotationCommand.js

@@ -0,0 +1,84 @@
+/**
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ */
+
+/**
+ * @param object THREE.Object3D
+ * @param newRotation THREE.Euler
+ * @param optionalOldRotation THREE.Euler
+ * @constructor
+ */
+
+var SetRotationCommand = function ( object, newRotation, optionalOldRotation ) {
+
+	Command.call( this );
+
+	this.type = 'SetRotationCommand';
+	this.name = 'Set Rotation';
+	this.updatable = true;
+
+	this.object = object;
+
+	if ( object !== undefined && newRotation !== undefined ) {
+
+		this.oldRotation = object.rotation.clone();
+		this.newRotation = newRotation.clone();
+
+	}
+
+	if ( optionalOldRotation !== undefined ) {
+
+		this.oldRotation = optionalOldRotation.clone();
+
+	}
+
+};
+
+SetRotationCommand.prototype = {
+
+	execute: function () {
+
+		this.object.rotation.copy( this.newRotation );
+		this.object.updateMatrixWorld( true );
+		this.editor.signals.objectChanged.dispatch( this.object );
+
+	},
+
+	undo: function () {
+
+		this.object.rotation.copy( this.oldRotation );
+		this.object.updateMatrixWorld( true );
+		this.editor.signals.objectChanged.dispatch( this.object );
+
+	},
+
+	update: function ( command ) {
+
+		this.newRotation.copy( command.newRotation );
+
+	},
+
+	toJSON: function () {
+
+		var output = Command.prototype.toJSON.call( this );
+
+		output.objectUuid = this.object.uuid;
+		output.oldRotation = this.oldRotation.toArray();
+		output.newRotation = this.newRotation.toArray();
+
+		return output;
+
+	},
+
+	fromJSON: function ( json ) {
+
+		Command.prototype.fromJSON.call( this, json );
+
+		this.object = this.editor.objectByUuid( json.objectUuid );
+		this.oldRotation = new THREE.Euler().fromArray( json.oldRotation );
+		this.newRotation = new THREE.Euler().fromArray( json.newRotation );
+
+	}
+
+};

+ 84 - 0
editor/js/commands/SetScaleCommand.js

@@ -0,0 +1,84 @@
+/**
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ */
+
+/**
+ * @param object THREE.Object3D
+ * @param newScale THREE.Vector3
+ * @param optionalOldScale THREE.Vector3
+ * @constructor
+ */
+
+var SetScaleCommand = function ( object, newScale, optionalOldScale ) {
+
+	Command.call( this );
+
+	this.type = 'SetScaleCommand';
+	this.name = 'Set Scale';
+	this.updatable = true;
+
+	this.object = object;
+
+	if ( object !== undefined && newScale !== undefined ) {
+
+		this.oldScale = object.scale.clone();
+		this.newScale = newScale.clone();
+
+	}
+
+	if ( optionalOldScale !== undefined ) {
+
+		this.oldScale = optionalOldScale.clone();
+
+	}
+
+};
+
+SetScaleCommand.prototype = {
+
+	execute: function () {
+
+		this.object.scale.copy( this.newScale );
+		this.object.updateMatrixWorld( true );
+		this.editor.signals.objectChanged.dispatch( this.object );
+
+	},
+
+	undo: function () {
+
+		this.object.scale.copy( this.oldScale );
+		this.object.updateMatrixWorld( true );
+		this.editor.signals.objectChanged.dispatch( this.object );
+
+	},
+
+	update: function ( command ) {
+
+		this.newScale.copy( command.newScale );
+
+	},
+
+	toJSON: function () {
+
+		var output = Command.prototype.toJSON.call( this );
+
+		output.objectUuid = this.object.uuid;
+		output.oldScale = this.oldScale.toArray();
+		output.newScale = this.newScale.toArray();
+
+		return output;
+
+	},
+
+	fromJSON: function ( json ) {
+
+		Command.prototype.fromJSON.call( this, json );
+
+		this.object = this.editor.objectByUuid( json.objectUuid );
+		this.oldScale = new THREE.Vector3().fromArray( json.oldScale );
+		this.newScale = new THREE.Vector3().fromArray( json.newScale );
+
+	}
+
+};

+ 100 - 0
editor/js/commands/SetSceneCommand.js

@@ -0,0 +1,100 @@
+/**
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ */
+
+/**
+ * @param scene containing children to import
+ * @constructor
+ */
+
+var SetSceneCommand = function ( scene ) {
+
+	Command.call( this );
+
+	this.type = 'SetSceneCommand';
+	this.name = 'Set Scene';
+
+	this.cmdArray = [];
+
+	if ( scene !== undefined ) {
+
+		this.cmdArray.push( new SetUuidCommand( this.editor.scene, scene.uuid ) );
+		this.cmdArray.push( new SetValueCommand( this.editor.scene, 'name', scene.name ) );
+		this.cmdArray.push( new SetValueCommand( this.editor.scene, 'userData', JSON.parse( JSON.stringify( scene.userData ) ) ) );
+
+		while ( scene.children.length > 0 ) {
+
+			var child = scene.children.pop();
+			this.cmdArray.push( new AddObjectCommand( child ) );
+
+		}
+
+	}
+
+};
+
+SetSceneCommand.prototype = {
+
+	execute: function () {
+
+		this.editor.signals.sceneGraphChanged.active = false;
+
+		for ( var i = 0; i < this.cmdArray.length; i ++ ) {
+
+			this.cmdArray[ i ].execute();
+
+		}
+
+		this.editor.signals.sceneGraphChanged.active = true;
+		this.editor.signals.sceneGraphChanged.dispatch();
+
+	},
+
+	undo: function () {
+
+		this.editor.signals.sceneGraphChanged.active = false;
+
+		for ( var i = this.cmdArray.length - 1; i >= 0; i -- ) {
+
+			this.cmdArray[ i ].undo();
+
+		}
+
+		this.editor.signals.sceneGraphChanged.active = true;
+		this.editor.signals.sceneGraphChanged.dispatch();
+
+	},
+
+	toJSON: function () {
+
+		var output = Command.prototype.toJSON.call( this );
+
+		var cmds = [];
+		for ( var i = 0; i < this.cmdArray.length; i ++ ) {
+
+			cmds.push( this.cmdArray[ i ].toJSON() );
+
+		}
+		output.cmds = cmds;
+
+		return output;
+
+	},
+
+	fromJSON: function ( json ) {
+
+		Command.prototype.fromJSON.call( this, json );
+
+		var cmds = json.cmds;
+		for ( var i = 0; i < cmds.length; i ++ ) {
+
+			var cmd = new window[ cmds[ i ].type ]();	// creates a new object of type "json.type"
+			cmd.fromJSON( cmds[ i ] );
+			this.cmdArray.push( cmd );
+
+		}
+
+	}
+
+};

+ 88 - 0
editor/js/commands/SetScriptValueCommand.js

@@ -0,0 +1,88 @@
+/**
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ */
+
+/**
+ * @param object THREE.Object3D
+ * @param script javascript object
+ * @param attributeName string
+ * @param newValue string, object
+ * @param cursorPosition javascript object with format {line: 2, ch: 3}
+ * @constructor
+ */
+
+var SetScriptValueCommand = function ( object, script, attributeName, newValue, cursorPosition ) {
+
+	Command.call( this );
+
+	this.type = 'SetScriptValueCommand';
+	this.name = 'Set Script.' + attributeName;
+	this.updatable = true;
+
+	this.object = object;
+	this.script = script;
+
+	this.attributeName = attributeName;
+	this.oldValue = ( script !== undefined ) ? script[ this.attributeName ] : undefined;
+	this.newValue = newValue;
+	this.cursorPosition = cursorPosition;
+
+};
+
+SetScriptValueCommand.prototype = {
+
+	execute: function () {
+
+		this.script[ this.attributeName ] = this.newValue;
+
+		this.editor.signals.scriptChanged.dispatch();
+		this.editor.signals.refreshScriptEditor.dispatch( this.object, this.script, this.cursorPosition );
+
+	},
+
+	undo: function () {
+
+		this.script[ this.attributeName ] = this.oldValue;
+
+		this.editor.signals.scriptChanged.dispatch();
+		this.editor.signals.refreshScriptEditor.dispatch( this.object, this.script, this.cursorPosition );
+
+	},
+
+	update: function ( cmd ) {
+
+		this.cursorPosition = cmd.cursorPosition;
+		this.newValue = cmd.newValue;
+
+	},
+
+	toJSON: function () {
+
+		var output = Command.prototype.toJSON.call( this );
+
+		output.objectUuid = this.object.uuid;
+		output.index = this.editor.scripts[ this.object.uuid ].indexOf( this.script );
+		output.attributeName = this.attributeName;
+		output.oldValue = this.oldValue;
+		output.newValue = this.newValue;
+		output.cursorPosition = this.cursorPosition;
+
+		return output;
+
+	},
+
+	fromJSON: function ( json ) {
+
+		Command.prototype.fromJSON.call( this, json );
+
+		this.oldValue = json.oldValue;
+		this.newValue = json.newValue;
+		this.attributeName = json.attributeName;
+		this.object = this.editor.objectByUuid( json.objectUuid );
+		this.script = this.editor.scripts[ json.objectUuid ][ json.index ];
+		this.cursorPosition = json.cursorPosition;
+
+	}
+
+};

+ 71 - 0
editor/js/commands/SetUuidCommand.js

@@ -0,0 +1,71 @@
+/**
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ */
+
+/**
+ * @param object THREE.Object3D
+ * @param newUuid string
+ * @constructor
+ */
+
+var SetUuidCommand = function ( object, newUuid ) {
+
+	Command.call( this );
+
+	this.type = 'SetUuidCommand';
+	this.name = 'Update UUID';
+
+	this.object = object;
+
+	this.oldUuid = ( object !== undefined ) ? object.uuid : undefined;
+	this.newUuid = newUuid;
+
+};
+
+SetUuidCommand.prototype = {
+
+	execute: function () {
+
+		this.object.uuid = this.newUuid;
+		this.editor.signals.objectChanged.dispatch( this.object );
+		this.editor.signals.sceneGraphChanged.dispatch();
+
+	},
+
+	undo: function () {
+
+		this.object.uuid = this.oldUuid;
+		this.editor.signals.objectChanged.dispatch( this.object );
+		this.editor.signals.sceneGraphChanged.dispatch();
+
+	},
+
+	toJSON: function () {
+
+		var output = Command.prototype.toJSON.call( this );
+
+		output.oldUuid = this.oldUuid;
+		output.newUuid = this.newUuid;
+
+		return output;
+
+	},
+
+	fromJSON: function ( json ) {
+
+		Command.prototype.fromJSON.call( this, json );
+
+		this.oldUuid = json.oldUuid;
+		this.newUuid = json.newUuid;
+		this.object = this.editor.objectByUuid( json.oldUuid );
+
+		if ( this.object === undefined ) {
+
+			this.object = this.editor.objectByUuid( json.newUuid );
+
+		}
+
+	}
+
+};

+ 76 - 0
editor/js/commands/SetValueCommand.js

@@ -0,0 +1,76 @@
+/**
+ * @author dforrer / https://github.com/dforrer
+ * Developed as part of a project at University of Applied Sciences and Arts Northwestern Switzerland (www.fhnw.ch)
+ */
+
+/**
+ * @param object THREE.Object3D
+ * @param attributeName string
+ * @param newValue number, string, boolean or object
+ * @constructor
+ */
+
+var SetValueCommand = function ( object, attributeName, newValue ) {
+
+	Command.call( this );
+
+	this.type = 'SetValueCommand';
+	this.name = 'Set ' + attributeName;
+	this.updatable = true;
+
+	this.object = object;
+	this.attributeName = attributeName;
+	this.oldValue = ( object !== undefined ) ? object[ attributeName ] : undefined;
+	this.newValue = newValue;
+
+};
+
+SetValueCommand.prototype = {
+
+	execute: function () {
+
+		this.object[ this.attributeName ] = this.newValue;
+		this.editor.signals.objectChanged.dispatch( this.object );
+		// this.editor.signals.sceneGraphChanged.dispatch();
+
+	},
+
+	undo: function () {
+
+		this.object[ this.attributeName ] = this.oldValue;
+		this.editor.signals.objectChanged.dispatch( this.object );
+		// this.editor.signals.sceneGraphChanged.dispatch();
+
+	},
+
+	update: function ( cmd ) {
+
+		this.newValue = cmd.newValue;
+
+	},
+
+	toJSON: function () {
+
+		var output = Command.prototype.toJSON.call( this );
+
+		output.objectUuid = this.object.uuid;
+		output.attributeName = this.attributeName;
+		output.oldValue = this.oldValue;
+		output.newValue = this.newValue;
+
+		return output;
+
+	},
+
+	fromJSON: function ( json ) {
+
+		Command.prototype.fromJSON.call( this, json );
+
+		this.attributeName = json.attributeName;
+		this.oldValue = json.oldValue;
+		this.newValue = json.newValue;
+		this.object = this.editor.objectByUuid( json.objectUuid );
+
+	}
+
+};

+ 21 - 21
editor/js/libs/app.js

@@ -27,6 +27,7 @@ var APP = {
 			renderer = new THREE.WebGLRenderer( { antialias: true } );
 			renderer = new THREE.WebGLRenderer( { antialias: true } );
 			renderer.setClearColor( 0x000000 );
 			renderer.setClearColor( 0x000000 );
 			renderer.setPixelRatio( window.devicePixelRatio );
 			renderer.setPixelRatio( window.devicePixelRatio );
+			if ( json.project.shadows ) renderer.shadowMap.enabled = true;
 			this.dom = renderer.domElement;
 			this.dom = renderer.domElement;
 
 
 			this.setScene( loader.parse( json.scene ) );
 			this.setScene( loader.parse( json.scene ) );
@@ -98,7 +99,6 @@ var APP = {
 			camera.aspect = this.width / this.height;
 			camera.aspect = this.width / this.height;
 			camera.updateProjectionMatrix();
 			camera.updateProjectionMatrix();
 
 
-
 			if ( vr === true ) {
 			if ( vr === true ) {
 
 
 				if ( camera.parent === null ) {
 				if ( camera.parent === null ) {
@@ -157,7 +157,7 @@ var APP = {
 
 
 		};
 		};
 
 
-		var dispatch = function ( array, event ) {
+		function dispatch( array, event ) {
 
 
 			for ( var i = 0, l = array.length; i < l; i ++ ) {
 			for ( var i = 0, l = array.length; i < l; i ++ ) {
 
 
@@ -173,11 +173,11 @@ var APP = {
 
 
 			}
 			}
 
 
-		};
+		}
 
 
 		var prevTime, request;
 		var prevTime, request;
 
 
-		var animate = function ( time ) {
+		function animate( time ) {
 
 
 			request = requestAnimationFrame( animate );
 			request = requestAnimationFrame( animate );
 
 
@@ -196,7 +196,7 @@ var APP = {
 
 
 			prevTime = time;
 			prevTime = time;
 
 
-		};
+		}
 
 
 		this.play = function () {
 		this.play = function () {
 
 
@@ -233,53 +233,53 @@ var APP = {
 
 
 		//
 		//
 
 
-		var onDocumentKeyDown = function ( event ) {
+		function onDocumentKeyDown( event ) {
 
 
 			dispatch( events.keydown, event );
 			dispatch( events.keydown, event );
 
 
-		};
+		}
 
 
-		var onDocumentKeyUp = function ( event ) {
+		function onDocumentKeyUp( event ) {
 
 
 			dispatch( events.keyup, event );
 			dispatch( events.keyup, event );
 
 
-		};
+		}
 
 
-		var onDocumentMouseDown = function ( event ) {
+		function onDocumentMouseDown( event ) {
 
 
 			dispatch( events.mousedown, event );
 			dispatch( events.mousedown, event );
 
 
-		};
+		}
 
 
-		var onDocumentMouseUp = function ( event ) {
+		function onDocumentMouseUp( event ) {
 
 
 			dispatch( events.mouseup, event );
 			dispatch( events.mouseup, event );
 
 
-		};
+		}
 
 
-		var onDocumentMouseMove = function ( event ) {
+		function onDocumentMouseMove( event ) {
 
 
 			dispatch( events.mousemove, event );
 			dispatch( events.mousemove, event );
 
 
-		};
+		}
 
 
-		var onDocumentTouchStart = function ( event ) {
+		function onDocumentTouchStart( event ) {
 
 
 			dispatch( events.touchstart, event );
 			dispatch( events.touchstart, event );
 
 
-		};
+		}
 
 
-		var onDocumentTouchEnd = function ( event ) {
+		function onDocumentTouchEnd( event ) {
 
 
 			dispatch( events.touchend, event );
 			dispatch( events.touchend, event );
 
 
-		};
+		}
 
 
-		var onDocumentTouchMove = function ( event ) {
+		function onDocumentTouchMove( event ) {
 
 
 			dispatch( events.touchmove, event );
 			dispatch( events.touchmove, event );
 
 
-		};
+		}
 
 
 	}
 	}
 
 

+ 0 - 160
editor/js/libs/tern-threejs/threejs.js

@@ -409,10 +409,6 @@
           "!type": "[]",
           "!type": "[]",
           "!doc": "Array of morph targets. Each morph target is a Javascript object:\n\t\t<code>{ name: \"targetName\", vertices: [ new THREE.Vector3(), ... ] }</code>\n\t\tMorph vertices match number and order of primary vertices."
           "!doc": "Array of morph targets. Each morph target is a Javascript object:\n\t\t<code>{ name: \"targetName\", vertices: [ new THREE.Vector3(), ... ] }</code>\n\t\tMorph vertices match number and order of primary vertices."
         },
         },
-        "morphColors": {
-          "!type": "[]",
-          "!doc": "Array of morph colors. Morph colors have similar structure as morph targets, each color set is a Javascript object:\n\t\t<code>morphColor = { name: \"colorName\", colors: [ new THREE.Color(), ... ] }</code>\n\t\tMorph colors can match either the number and order of faces (face colors) or the number of vertices (vertex colors)."
-        },
         "morphNormals": {
         "morphNormals": {
           "!type": "[]",
           "!type": "[]",
           "!doc": "Array of morph normals. Morph normals have similar structure as morph targets, each normal set is a Javascript object:\n\t\t<code>morphNormal = { name: \"NormalName\", normals: [ new THREE.Vector3(), ... ] }</code>"
           "!doc": "Array of morph normals. Morph normals have similar structure as morph targets, each normal set is a Javascript object:\n\t\t<code>morphNormal = { name: \"NormalName\", normals: [ new THREE.Vector3(), ... ] }</code>"
@@ -2088,106 +2084,6 @@
         "intensity": {
         "intensity": {
           "!type": "number",
           "!type": "number",
           "!doc": "Light's intensity.<br>\n\t\t\tDefault — *1.0*."
           "!doc": "Light's intensity.<br>\n\t\t\tDefault — *1.0*."
-        },
-        "onlyShadow": {
-          "!type": "bool",
-          "!doc": "If set to *true* light will only cast shadow but not contribute any lighting (as if *intensity* was 0 but cheaper to compute).<br>\n\t\t\tDefault — *false*."
-        },
-        "shadowCameraNear": {
-          "!type": "number",
-          "!doc": "Orthographic shadow camera frustum parameter.<br>\n\t\t\tDefault — *50*."
-        },
-        "shadowCameraFar": {
-          "!type": "number",
-          "!doc": "Orthographic shadow camera frustum parameter.<br>\n\t\t\tDefault — *5000*."
-        },
-        "shadowCameraLeft": {
-          "!type": "number",
-          "!doc": "Orthographic shadow camera frustum parameter.<br>\n\t\t\tDefault — *-500*."
-        },
-        "shadowCameraRight": {
-          "!type": "number",
-          "!doc": "Orthographic shadow camera frustum parameter.<br>\n\t\t\tDefault — *500*."
-        },
-        "shadowCameraTop": {
-          "!type": "number",
-          "!doc": "Orthographic shadow camera frustum parameter.<br>\n\t\t\tDefault — *500*."
-        },
-        "shadowCameraBottom": {
-          "!type": "number",
-          "!doc": "Orthographic shadow camera frustum parameter.<br>\n\t\t\tDefault — *-500*."
-        },
-        "shadowCameraVisible": {
-          "!type": "bool",
-          "!doc": "Show debug shadow camera frustum.<br>\n\t\t\tDefault — *false*."
-        },
-        "shadowBias": {
-          "!type": "number",
-          "!doc": "Shadow map bias, how much to add or subtract from the normalized depth when deciding whether a surface is in shadow.<br>\n\t\t\tDefault — *0*."
-        },
-        "shadowDarkness": {
-          "!type": "number",
-          "!doc": "Darkness of shadow casted by this light (from *0* to *1*).<br>\n\t\t\tDefault — *0.5*."
-        },
-        "shadowMapWidth": {
-          "!type": "number",
-          "!doc": "Shadow map texture width in pixels.<br>\n\t\t\tDefault — *512*."
-        },
-        "shadowMapHeight": {
-          "!type": "number",
-          "!doc": "Shadow map texture height in pixels.<br>\n\t\t\tDefault — *512*."
-        },
-        "shadowCascade": {
-          "!type": "bool",
-          "!doc": "**Experimental** If true, use a series of shadow maps in a cascade. This can give better z-depth resolution for a directional light. <br>\n\t\t\tDefault — *false*."
-        },
-        "shadowCascadeCount": {
-          "!type": "number",
-          "!doc": "Number of shadow maps to allocate in a cascade (one after another). <br>\n\t\t\tDefault — *2*."
-        },
-        "shadowCascadeOffset": {
-          "!type": "+THREE.Vector3",
-          "!doc": "A relative position to real camera where virtual shadow cameras are attached. A magic vector; scene and light orientation dependent. <br>\n\t\t\tDefault — *Three.Vector3( 0, 0, -1000 )*."
-        },
-        "shadowCascadeBias": {
-          "!type": "[]",
-          "!doc": "An array of shadowMapBias values for the corresponding shadow map in the cascade, near to far. <br>\n\t\t\tDefault — <strong>[ 0, 0, 0 ]</strong>."
-        },
-        "shadowCascadeWidth": {
-          "!type": "[]",
-          "!doc": "An array of shadowMapWidth values for the corresponding shadow map in the cascade, near to far. <br>\n\t\t\tDefault — <strong>[ 512, 512, 512 ]</strong>."
-        },
-        "shadowCascadeHeight": {
-          "!type": "[]",
-          "!doc": "An array of shadowMapHeight values for the corresponding shadow map in the cascade, near to far. <br>\n\t\t\tDefault — <strong>[ 512, 512, 512 ]</strong>."
-        },
-        "shadowCascadeNearZ": {
-          "!type": "[]",
-          "!doc": "An array of shadowMapNear values for the corresponding shadow map in the cascade, near to far. These typically start with -1.0 (near plane) and match with the previous shadowCascadeFarZ array value.<br>\n\t\t\tDefault — <strong>[ -1.000, 0.990, 0.998 ]</strong>."
-        },
-        "shadowCascadeFarZ": {
-          "!type": "[]",
-          "!doc": "An array of shadowMapFar values for the corresponding shadow map in the cascade, near to far. These typically match with the next shadowCascadeNearZ array value, ending in 1.0.<br>\n\t\t\tDefault — <strong>[ 0.990, 0.998, 1.000 ]</strong>."
-        },
-        "shadowCascadeArray": {
-          "!type": "[]",
-          "!doc": "Array of size shadowCascadeCount of [page:DirectionalLight THREE.DirectionalLight] objects. This holds the series of separate shadow maps in a cascade, near to far. Created internally."
-        },
-        "shadowMapSize": {
-          "!type": "+THREE.Vector2",
-          "!doc": "The shadowMapWidth and shadowMapHeight stored in a [page:Vector2 THREE.Vector2]. Set internally during rendering."
-        },
-        "shadowCamera": {
-          "!type": "+THREE.OrthographicCamera",
-          "!doc": "The shadow's view of the world. Computed internally during rendering from the shadowCamera* settings."
-        },
-        "shadowMatrix": {
-          "!type": "+THREE.Matrix4",
-          "!doc": "Model to shadow camera space, to compute location and depth in shadow map. Computed internally during rendering."
-        },
-        "shadowMap": {
-          "!type": "+THREE.WebGLRenderTarget",
-          "!doc": "The depth map generated using the shadowCamera; a location beyond a pixel's depth is in shadow. Computed internally during rendering."
         }
         }
       },
       },
       "!doc": "Affects objects using [page:MeshLambertMaterial] or [page:MeshPhongMaterial].",
       "!doc": "Affects objects using [page:MeshLambertMaterial] or [page:MeshPhongMaterial].",
@@ -2260,62 +2156,6 @@
         "exponent": {
         "exponent": {
           "!type": "number",
           "!type": "number",
           "!doc": "Rapidity of the falloff of light from its target direction.<br>\n\t\t\tDefault — *10.0*."
           "!doc": "Rapidity of the falloff of light from its target direction.<br>\n\t\t\tDefault — *10.0*."
-        },
-        "castShadow": {
-          "!type": "bool",
-          "!doc": "If set to *true* light will cast dynamic shadows. *Warning*: This is expensive and requires tweaking to get shadows looking right.<br>\n\t\t\tDefault — *false*."
-        },
-        "onlyShadow": {
-          "!type": "bool",
-          "!doc": "If set to *true* light will only cast shadow but not contribute any lighting (as if *intensity* was 0 but cheaper to compute).<br>\n\t\t\tDefault — *false*."
-        },
-        "shadowCameraNear": {
-          "!type": "number",
-          "!doc": "Perspective shadow camera frustum <em>near</em> parameter.<br>\n\t\t\tDefault — *50*."
-        },
-        "shadowCameraFar": {
-          "!type": "number",
-          "!doc": "Perspective shadow camera frustum <em>far</em> parameter.<br>\n\t\t\tDefault — *5000*."
-        },
-        "shadowCameraFov": {
-          "!type": "number",
-          "!doc": "Perspective shadow camera frustum <em>field of view</em> parameter.<br>\n\t\t\tDefault — *50*."
-        },
-        "shadowCameraVisible": {
-          "!type": "bool",
-          "!doc": "Show debug shadow camera frustum.<br>\n\t\t\tDefault — *false*."
-        },
-        "shadowBias": {
-          "!type": "number",
-          "!doc": "Shadow map bias, how much to add or subtract from the normalized depth when deciding whether a surface is in shadow.<br>\n\t\t\tDefault — *0*."
-        },
-        "shadowDarkness": {
-          "!type": "number",
-          "!doc": "Darkness of shadow casted by this light (from *0* to *1*).<br>\n\t\t\tDefault — *0.5*."
-        },
-        "shadowMapWidth": {
-          "!type": "number",
-          "!doc": "Shadow map texture width in pixels.<br>\n\t\t\tDefault — *512*."
-        },
-        "shadowMapHeight": {
-          "!type": "number",
-          "!doc": "Shadow map texture height in pixels.<br>\n\t\t\tDefault — *512*."
-        },
-        "shadowMapSize": {
-          "!type": "+THREE.Vector2",
-          "!doc": "The shadowMapWidth and shadowMapHeight stored in a [page:Vector2 THREE.Vector2]. Set internally during rendering."
-        },
-        "shadowCamera": {
-          "!type": "+THREE.PerspectiveCamera",
-          "!doc": "The shadow's view of the world. Computed internally during rendering from the shadowCamera* settings."
-        },
-        "shadowMatrix": {
-          "!type": "+THREE.Matrix4",
-          "!doc": "Model to shadow camera space, to compute location and depth in shadow map. Computed internally during rendering."
-        },
-        "shadowMap": {
-          "!type": "+THREE.WebGLRenderTarget",
-          "!doc": "The depth map generated using the shadowCamera; a location beyond a pixel's depth is in shadow. Computed internally during rendering."
         }
         }
       },
       },
       "!doc": "A point light that can cast shadow in one direction.",
       "!doc": "A point light that can cast shadow in one direction.",

+ 103 - 74
editor/js/libs/ui.js

@@ -12,6 +12,60 @@ UI.Element = function ( dom ) {
 
 
 UI.Element.prototype = {
 UI.Element.prototype = {
 
 
+	add: function () {
+
+		for ( var i = 0; i < arguments.length; i ++ ) {
+
+			var argument = arguments[ i ];
+
+			if ( argument instanceof UI.Element ) {
+
+				this.dom.appendChild( argument.dom );
+
+			} else {
+
+				console.error( 'UI.Element:', argument, 'is not an instance of UI.Element.' )
+
+			}
+
+		}
+
+		return this;
+
+	},
+
+	remove: function () {
+
+		for ( var i = 0; i < arguments.length; i ++ ) {
+
+			var argument = arguments[ i ];
+
+			if ( argument instanceof UI.Element ) {
+
+				this.dom.removeChild( argument.dom );
+
+			} else {
+
+				console.error( 'UI.Element:', argument, 'is not an instance of UI.Element.' )
+
+			}
+
+		}
+
+		return this;
+
+	},
+
+	clear: function () {
+
+		while ( this.dom.children.length ) {
+
+			this.dom.removeChild( this.dom.lastChild );
+
+		}
+
+	},
+
 	setId: function ( id ) {
 	setId: function ( id ) {
 
 
 		this.dom.id = id;
 		this.dom.id = id;
@@ -36,6 +90,8 @@ UI.Element.prototype = {
 
 
 		}
 		}
 
 
+		return this;
+
 	},
 	},
 
 
 	setDisabled: function ( value ) {
 	setDisabled: function ( value ) {
@@ -69,6 +125,7 @@ properties.forEach( function ( property ) {
 	UI.Element.prototype[ method ] = function () {
 	UI.Element.prototype[ method ] = function () {
 
 
 		this.setStyle( property, arguments );
 		this.setStyle( property, arguments );
+
 		return this;
 		return this;
 
 
 	};
 	};
@@ -93,78 +150,39 @@ events.forEach( function ( event ) {
 
 
 } );
 } );
 
 
+// Span
 
 
-// Panel
-
-UI.Panel = function () {
+UI.Span = function () {
 
 
 	UI.Element.call( this );
 	UI.Element.call( this );
 
 
-	var dom = document.createElement( 'div' );
-	dom.className = 'Panel';
-
-	this.dom = dom;
-
-	return this;
-};
-
-UI.Panel.prototype = Object.create( UI.Element.prototype );
-UI.Panel.prototype.constructor = UI.Panel;
-
-UI.Panel.prototype.add = function () {
-
-	for ( var i = 0; i < arguments.length; i ++ ) {
-
-		var argument = arguments[ i ];
-
-		if ( argument instanceof UI.Element ) {
-
-			this.dom.appendChild( argument.dom );
-
-		} else {
-
-			console.error( 'UI.Panel:', argument, 'is not an instance of UI.Element.' )
-
-		}
-
-	}
+	this.dom = document.createElement( 'span' );
 
 
 	return this;
 	return this;
 
 
 };
 };
 
 
+UI.Span.prototype = Object.create( UI.Element.prototype );
+UI.Span.prototype.constructor = UI.Span;
 
 
-UI.Panel.prototype.remove = function () {
-
-	for ( var i = 0; i < arguments.length; i ++ ) {
-
-		var argument = arguments[ i ];
 
 
-		if ( argument instanceof UI.Element ) {
-
-			this.dom.removeChild( argument.dom );
+// Panel
 
 
-		} else {
+UI.Panel = function () {
 
 
-			console.error( 'UI.Panel:', argument, 'is not an instance of UI.Element.' )
+	UI.Element.call( this );
 
 
-		}
+	var dom = document.createElement( 'div' );
+	dom.className = 'Panel';
 
 
-	}
+	this.dom = dom;
 
 
 	return this;
 	return this;
 
 
 };
 };
 
 
-UI.Panel.prototype.clear = function () {
-
-	while ( this.dom.children.length ) {
-
-		this.dom.removeChild( this.dom.lastChild );
-
-	}
-
-};
+UI.Panel.prototype = Object.create( UI.Element.prototype );
+UI.Panel.prototype.constructor = UI.Panel;
 
 
 
 
 // Collapsible Panel
 // Collapsible Panel
@@ -996,47 +1014,58 @@ UI.Button.prototype.setLabel = function ( value ) {
 };
 };
 
 
 
 
-// Dialog
+// Modal
 
 
-UI.Dialog = function ( value ) {
+UI.Modal = function ( value ) {
 
 
 	var scope = this;
 	var scope = this;
 
 
-	var dom = document.createElement( 'dialog' );
+	var dom = document.createElement( 'div' );
 
 
-	if ( dom.showModal === undefined ) {
+	dom.style.position = 'absolute';
+	dom.style.width = '100%';
+	dom.style.height = '100%';
+	dom.style.backgroundColor = 'rgba(0,0,0,0.5)';
+	dom.style.display = 'none';
+	dom.style.alignItems = 'center';
+	dom.style.justifyContent = 'center';
+	dom.addEventListener( 'click', function ( event ) {
 
 
-		// fallback
+		scope.hide();
 
 
-		dom = document.createElement( 'div' );
-		dom.style.display = 'none';
+	} );
 
 
-		dom.showModal = function () {
+	this.dom = dom;
 
 
-			dom.style.position = 'absolute';
-			dom.style.left = '100px';
-			dom.style.top = '100px';
-			dom.style.zIndex = 1;
-			dom.style.display = '';
+	this.container = new UI.Panel();
+	this.container.dom.style.width = '200px';
+	this.container.dom.style.padding = '20px';
+	this.container.dom.style.backgroundColor = '#ffffff';
+	this.container.dom.style.boxShadow = '0px 5px 10px rgba(0,0,0,0.5)';
 
 
-		};
+	this.add( this.container );
 
 
-	}
+	return this;
 
 
-	dom.className = 'Dialog';
+};
 
 
-	this.dom = dom;
+UI.Modal.prototype = Object.create( UI.Element.prototype );
+UI.Modal.prototype.constructor = UI.Modal;
+
+UI.Modal.prototype.show = function ( content ) {
+
+	this.container.clear();
+	this.container.add( content );
+
+	this.dom.style.display = 'flex';
 
 
 	return this;
 	return this;
 
 
 };
 };
 
 
-UI.Dialog.prototype = Object.create( UI.Panel.prototype );
-UI.Dialog.prototype.constructor = UI.Dialog;
-
-UI.Dialog.prototype.showModal = function () {
+UI.Modal.prototype.hide = function () {
 
 
-	this.dom.showModal();
+	this.dom.style.display = 'none';
 
 
 	return this;
 	return this;
 
 

+ 33 - 2
editor/js/libs/ui.three.js

@@ -158,12 +158,12 @@ UI.Outliner = function ( editor ) {
 
 
 			if ( item.nextSibling === null ) {
 			if ( item.nextSibling === null ) {
 
 
-				editor.moveObject( object, editor.scene );
+				editor.execute( new MoveObjectCommand( object, editor.scene ) );
 
 
 			} else {
 			} else {
 
 
 				var nextObject = scene.getObjectById( item.nextSibling.value );
 				var nextObject = scene.getObjectById( item.nextSibling.value );
-				editor.moveObject( object, nextObject.parent, nextObject );
+				editor.execute( new MoveObjectCommand( object, nextObject.parent, nextObject ) );
 
 
 			}
 			}
 
 
@@ -309,3 +309,34 @@ UI.Outliner.prototype.setValue = function ( value ) {
 	return this;
 	return this;
 
 
 };
 };
+
+UI.THREE = {};
+
+UI.THREE.Boolean = function ( boolean, text ) {
+
+	UI.Span.call( this );
+
+	this.setMarginRight( '10px' )
+
+	this.checkbox = new UI.Checkbox( boolean );
+	this.text = new UI.Text( text ).setMarginLeft( '3px' );
+
+	this.add( this.checkbox );
+	this.add( this.text );
+
+};
+
+UI.THREE.Boolean.prototype = Object.create( UI.Span.prototype );
+UI.THREE.Boolean.prototype.constructor = UI.THREE.Boolean;
+
+UI.THREE.Boolean.prototype.getValue = function () {
+
+	return this.checkbox.getValue();
+
+};
+
+UI.THREE.Boolean.prototype.setValue = function ( value ) {
+
+	return this.checkbox.setValue( value );
+
+};

+ 1 - 1
examples/canvas_geometry_panorama_fisheye.html

@@ -103,7 +103,7 @@
 				document.addEventListener( 'mousemove', onDocumentMouseMove, false );
 				document.addEventListener( 'mousemove', onDocumentMouseMove, false );
 				document.addEventListener( 'mouseup', onDocumentMouseUp, false );
 				document.addEventListener( 'mouseup', onDocumentMouseUp, false );
 				document.addEventListener( 'mousewheel', onDocumentMouseWheel, false );
 				document.addEventListener( 'mousewheel', onDocumentMouseWheel, false );
-				document.addEventListener( 'DOMMouseScroll', onDocumentMouseWheel, false);
+				document.addEventListener( 'MozMousePixelScroll', onDocumentMouseWheel, false);
 
 
 				document.addEventListener( 'touchstart', onDocumentTouchStart, false );
 				document.addEventListener( 'touchstart', onDocumentTouchStart, false );
 				document.addEventListener( 'touchmove', onDocumentTouchMove, false );
 				document.addEventListener( 'touchmove', onDocumentTouchMove, false );

+ 8 - 4
examples/canvas_geometry_text.html

@@ -23,8 +23,8 @@
 
 
 		<script src="js/libs/stats.min.js"></script>
 		<script src="js/libs/stats.min.js"></script>
 
 
-		<!-- load the font file from canvas-text -->
-
+		<script src="js/geometries/TextGeometry.js"></script>
+		<script src="js/utils/FontUtils.js"></script>
 		<script src="fonts/helvetiker_regular.typeface.js"></script>
 		<script src="fonts/helvetiker_regular.typeface.js"></script>
 
 
 
 
@@ -90,8 +90,12 @@
 				text3d.computeBoundingBox();
 				text3d.computeBoundingBox();
 				var centerOffset = -0.5 * ( text3d.boundingBox.max.x - text3d.boundingBox.min.x );
 				var centerOffset = -0.5 * ( text3d.boundingBox.max.x - text3d.boundingBox.min.x );
 
 
-				var textMaterial = new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff, overdraw: 0.5 } );
-				text = new THREE.Mesh( text3d, textMaterial );
+				var material = new THREE.MeshFaceMaterial( [
+					new THREE.MeshBasicMaterial( { color: Math.random() * 0xffffff, overdraw: 0.5 } ),
+					new THREE.MeshBasicMaterial( { color: 0x000000, overdraw: 0.5 } )
+				] );
+
+				text = new THREE.Mesh( text3d, material );
 
 
 				text.position.x = centerOffset;
 				text.position.x = centerOffset;
 				text.position.y = 100;
 				text.position.y = 100;

+ 0 - 1
examples/canvas_materials.html

@@ -73,7 +73,6 @@
 					new THREE.MeshBasicMaterial( { color: 0xff0000, blending: THREE.AdditiveBlending } ),
 					new THREE.MeshBasicMaterial( { color: 0xff0000, blending: THREE.AdditiveBlending } ),
 					new THREE.MeshLambertMaterial( { color: 0xffffff, shading: THREE.FlatShading, overdraw: 0.5 } ),
 					new THREE.MeshLambertMaterial( { color: 0xffffff, shading: THREE.FlatShading, overdraw: 0.5 } ),
 					new THREE.MeshLambertMaterial( { color: 0xffffff, shading: THREE.SmoothShading, overdraw: 0.5 } ),
 					new THREE.MeshLambertMaterial( { color: 0xffffff, shading: THREE.SmoothShading, overdraw: 0.5 } ),
-					new THREE.MeshDepthMaterial( { overdraw: 0.5 } ),
 					new THREE.MeshNormalMaterial( { overdraw: 0.5 } ),
 					new THREE.MeshNormalMaterial( { overdraw: 0.5 } ),
 					new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'textures/land_ocean_ice_cloud_2048.jpg' ) } ),
 					new THREE.MeshBasicMaterial( { map: THREE.ImageUtils.loadTexture( 'textures/land_ocean_ice_cloud_2048.jpg' ) } ),
 					new THREE.MeshBasicMaterial( { envMap: THREE.ImageUtils.loadTexture( 'textures/envmap.png', THREE.SphericalReflectionMapping ), overdraw: 0.5 } )
 					new THREE.MeshBasicMaterial( { envMap: THREE.ImageUtils.loadTexture( 'textures/envmap.png', THREE.SphericalReflectionMapping ), overdraw: 0.5 } )

+ 0 - 238
examples/canvas_materials_depth.html

@@ -1,238 +0,0 @@
-<!DOCTYPE html>
-<html lang="en">
-	<head>
-		<title>three.js canvas - depth material</title>
-		<meta charset="utf-8">
-		<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
-		<style>
-			body {
-				font-family: Monospace;
-				background-color: #000000;
-				margin: 0px;
-				overflow: hidden;
-			}
-		</style>
-	</head>
-	<body>
-
-		<script src="../build/three.min.js"></script>
-
-		<script src="js/renderers/Projector.js"></script>
-		<script src="js/renderers/CanvasRenderer.js"></script>
-
-		<script src="js/libs/stats.min.js"></script>
-
-		<script>
-
-			var container, stats;
-
-			var camera, scene, renderer;
-
-			var cube, plane, objects = [];
-
-			var targetRotation = 0;
-			var targetRotationOnMouseDown = 0;
-
-			var mouseX = 0;
-			var mouseXOnMouseDown = 0;
-
-			var moveForward = false;
-			var moveBackwards = false;
-			var moveLeft = false;
-			var moveRight = false;
-			var moveUp = false;
-			var moveDown = false;
-
-			var targetMoveLeft = false;
-			var targetMoveRight = false;
-
-			var debugContext;
-
-			init();
-			animate();
-
-			function init() {
-
-				container = document.createElement( 'div' );
-				document.body.appendChild( container );
-
-				camera = new THREE.PerspectiveCamera( 45, window.innerWidth / window.innerHeight, 1, 2000 );
-				camera.position.set( 1000, 1000, 1000 );
-				camera.target = new THREE.Vector3( 0, 150, 0 );
-
-				scene = new THREE.Scene();
-
-				// Plane
-
-				var material = new THREE.MeshDepthMaterial( { side: THREE.DoubleSide, overdraw: 0.5 } );
-
-				plane = new THREE.Mesh( new THREE.PlaneBufferGeometry( 1000, 1000, 10, 10 ), material );
-				plane.position.y = - 100;
-				plane.rotation.x = - Math.PI / 2;
-				scene.add( plane );
-
-				// Cubes
-
-				geometry = new THREE.BoxGeometry( 100, 100, 100 );
-				material = new THREE.MeshDepthMaterial( { overdraw: 0.5 } );
-
-				for ( var i = 0; i < 20; i ++ ) {
-
-					cube = new THREE.Mesh( geometry, material );
-
-					cube.position.x = ( i % 5 ) * 200 - 400;
-					cube.position.z = Math.floor( i / 5 ) * 200 - 350;
-
-					cube.rotation.x = Math.random() * 200 - 100;
-					cube.rotation.y = Math.random() * 200 - 100;
-					cube.rotation.z = Math.random() * 200 - 100;
-
-					scene.add( cube );
-
-					objects.push( cube );
-
-				}
-
-				renderer = new THREE.CanvasRenderer();
-				renderer.setPixelRatio( window.devicePixelRatio );
-				renderer.setSize( window.innerWidth, window.innerHeight );
-				container.appendChild( renderer.domElement );
-
-				var debugCanvas = document.createElement( 'canvas' );
-				debugCanvas.width = 512;
-				debugCanvas.height = 512;
-				debugCanvas.style.position = 'absolute';
-				debugCanvas.style.top = '0px';
-				debugCanvas.style.left = '0px';
-
-				container.appendChild( debugCanvas );
-
-				debugContext = debugCanvas.getContext( '2d' );
-				debugContext.setTransform( 1, 0, 0, 1, 256, 256 );
-				debugContext.strokeStyle = '#808080';
-
-				stats = new Stats();
-				stats.domElement.style.position = 'absolute';
-				stats.domElement.style.top = '0px';
-				container.appendChild(stats.domElement);
-
-				document.addEventListener( 'keydown', onDocumentKeyDown, false );
-				document.addEventListener( 'keyup', onDocumentKeyUp, false );
-
-				//
-
-				window.addEventListener( 'resize', onWindowResize, false );
-
-			}
-
-			function onWindowResize() {
-
-				camera.aspect = window.innerWidth / window.innerHeight;
-				camera.updateProjectionMatrix();
-
-				renderer.setSize( window.innerWidth, window.innerHeight );
-
-			}
-
-			function onDocumentKeyDown( event ) {
-
-				switch ( event.keyCode ) {
-
-					case 38: moveForward = true; break; // up
-					case 40: moveBackwards = true; break; // down
-					case 37: moveLeft = true; break; // left
-					case 39: moveRight = true; break; // right
-					case 87: moveUp = true; break; // w
-					case 83: moveDown = true; break; // s
-					case 65: targetMoveLeft = true; break; // a
-					case 68: targetMoveRight = true; break; // d
-
-				}
-
-			}
-
-			function onDocumentKeyUp( event ) {
-
-				switch ( event.keyCode ) {
-
-					case 38: moveForward = false; break; // up
-					case 40: moveBackwards = false; break; // down
-					case 37: moveLeft = false; break; // left
-					case 39: moveRight = false; break; // right
-					case 87: moveUp = false; break; // w
-					case 83: moveDown = false; break; // s
-					case 65: targetMoveLeft = false; break; // a
-					case 68: targetMoveRight = false; break; // d
-
-				}
-
-			}
-
-			//
-
-			function animate() {
-
-				requestAnimationFrame( animate );
-
-				render();
-				stats.update();
-
-			}
-
-			function render() {
-
-				if ( moveForward ) camera.position.z -= 10;
-				if ( moveBackwards ) camera.position.z += 10;
-
-				if ( moveLeft ) camera.position.x -= 10;
-				if ( moveRight ) camera.position.x += 10;
-
-				if ( moveUp ) camera.position.y += 10;
-				if ( moveDown ) camera.position.y -= 10;
-
-				if ( targetMoveLeft ) camera.target.x -= 10;
-				if ( targetMoveRight ) camera.target.x += 10;
-
-				camera.lookAt( camera.target );
-
-				debugContext.clearRect( - 256, - 256, 512, 512 );
-
-				debugContext.beginPath();
-
-				// center
-				debugContext.moveTo( - 10, 0 );
-				debugContext.lineTo( 10, 0 );
-				debugContext.moveTo( 0, - 10 );
-				debugContext.lineTo( 0, 10 );
-
-				// camera
-
-				debugContext.moveTo( camera.position.x * 0.1, camera.position.z * 0.1 );
-				debugContext.lineTo( camera.target.x * 0.1, camera.target.z * 0.1 );
-				debugContext.rect( camera.position.x * 0.1 - 5, camera.position.z * 0.1 - 5, 10, 10 );
-				debugContext.rect( camera.target.x * 0.1 - 5, camera.target.z * 0.1 - 5, 10, 10 );
-				debugContext.rect( - 50, - 50, 100, 100 );
-
-				for ( var i = 0; i < objects.length; i++ ) {
-
-					var object = objects[ i ];
-
-					object.rotation.x += 0.01;
-					object.rotation.y += 0.005;
-					object.position.y = Math.sin( object.rotation.x ) * 200 + 200;
-
-					debugContext.rect( object.position.x * 0.1 - 5, object.position.z * 0.1 - 5, 10, 10 );
-
-				}
-
-				debugContext.closePath();
-				debugContext.stroke();
-
-				renderer.render( scene, camera );
-
-			}
-
-		</script>
-
-	</body>
-</html>

+ 10 - 4
examples/canvas_morphtargets_horse.html

@@ -54,25 +54,31 @@
 
 
 				//
 				//
 
 
-				var light = new THREE.DirectionalLight( 0xefefff, 2 );
+				var light = new THREE.DirectionalLight( 0xefefff, 1.5 );
 				light.position.set( 1, 1, 1 ).normalize();
 				light.position.set( 1, 1, 1 ).normalize();
 				scene.add( light );
 				scene.add( light );
 
 
-				var light = new THREE.DirectionalLight( 0xffefef, 2 );
+				var light = new THREE.DirectionalLight( 0xffefef, 1.5 );
 				light.position.set( -1, -1, -1 ).normalize();
 				light.position.set( -1, -1, -1 ).normalize();
 				scene.add( light );
 				scene.add( light );
 
 
 				var loader = new THREE.JSONLoader();
 				var loader = new THREE.JSONLoader();
 				loader.load( "models/animated/horse.js", function ( geometry ) {
 				loader.load( "models/animated/horse.js", function ( geometry ) {
 
 
-					mesh = new THREE.Mesh( geometry, new THREE.MeshLambertMaterial( { color: 0x606060, morphTargets: true, overdraw: 0.5 } ) );
+					var material = new THREE.MeshLambertMaterial( {
+						vertexColors: THREE.FaceColors,
+						morphTargets: true,
+						overdraw: 0.5
+					} );
+
+					mesh = new THREE.Mesh( geometry, material );
 					mesh.scale.set( 1.5, 1.5, 1.5 );
 					mesh.scale.set( 1.5, 1.5, 1.5 );
 					scene.add( mesh );
 					scene.add( mesh );
 
 
 					mixer = new THREE.AnimationMixer( mesh );
 					mixer = new THREE.AnimationMixer( mesh );
 
 
 					var clip = THREE.AnimationClip.CreateFromMorphTargetSequence( 'gallop', geometry.morphTargets, 30 );
 					var clip = THREE.AnimationClip.CreateFromMorphTargetSequence( 'gallop', geometry.morphTargets, 30 );
-					mixer.addAction( new THREE.AnimationAction( clip ).warpToDuration( 1.5 ) );
+					mixer.clipAction( clip ).setDuration( 1 ).play();
 
 
 				} );
 				} );
 
 

+ 12 - 3
examples/index.html

@@ -190,6 +190,7 @@
 		var files = {
 		var files = {
 			"webgl": [
 			"webgl": [
 				"webgl_animation_cloth",
 				"webgl_animation_cloth",
+				"webgl_animation_scene",
 				"webgl_animation_skinning_blending",
 				"webgl_animation_skinning_blending",
 				"webgl_animation_skinning_morph",
 				"webgl_animation_skinning_morph",
 				"webgl_camera",
 				"webgl_camera",
@@ -224,7 +225,8 @@
 				"webgl_geometry_terrain_fog",
 				"webgl_geometry_terrain_fog",
 				"webgl_geometry_terrain_raycast",
 				"webgl_geometry_terrain_raycast",
 				"webgl_geometry_text",
 				"webgl_geometry_text",
-				"webgl_geometry_text2",
+				"webgl_geometry_text_earcut",
+				"webgl_geometry_text_pnltri",
 				"webgl_gpgpu_birds",
 				"webgl_gpgpu_birds",
 				"webgl_gpu_particle_system",
 				"webgl_gpu_particle_system",
 				"webgl_hdr",
 				"webgl_hdr",
@@ -248,6 +250,7 @@
 				"webgl_lines_dashed",
 				"webgl_lines_dashed",
 				"webgl_lines_sphere",
 				"webgl_lines_sphere",
 				"webgl_lines_splines",
 				"webgl_lines_splines",
+				"webgl_loader_3mf",
 				"webgl_loader_amf",
 				"webgl_loader_amf",
 				"webgl_loader_assimp2json",
 				"webgl_loader_assimp2json",
 				"webgl_loader_awd",
 				"webgl_loader_awd",
@@ -283,7 +286,6 @@
 				"webgl_lod",
 				"webgl_lod",
 				"webgl_marchingcubes",
 				"webgl_marchingcubes",
 				"webgl_materials",
 				"webgl_materials",
-				"webgl_materials2",
 				"webgl_materials_blending",
 				"webgl_materials_blending",
 				"webgl_materials_blending_custom",
 				"webgl_materials_blending_custom",
 				"webgl_materials_bumpmap",
 				"webgl_materials_bumpmap",
@@ -311,6 +313,11 @@
 				"webgl_materials_texture_manualmipmap",
 				"webgl_materials_texture_manualmipmap",
 				"webgl_materials_texture_pvrtc",
 				"webgl_materials_texture_pvrtc",
 				"webgl_materials_texture_tga",
 				"webgl_materials_texture_tga",
+				"webgl_materials_variations_basic",
+				"webgl_materials_variations_lambert",
+				"webgl_materials_variations_phong",
+				"webgl_materials_variations_physical",
+				"webgl_materials_variations_physical2",
 				"webgl_materials_video",
 				"webgl_materials_video",
 				"webgl_materials_wireframe",
 				"webgl_materials_wireframe",
 				"webgl_mirror",
 				"webgl_mirror",
@@ -348,6 +355,7 @@
 				"webgl_postprocessing_godrays",
 				"webgl_postprocessing_godrays",
 				"webgl_postprocessing_ssao",
 				"webgl_postprocessing_ssao",
 				"webgl_raycast_texture",
 				"webgl_raycast_texture",
+				"webgl_read_float_buffer",
 				"webgl_rtt",
 				"webgl_rtt",
 				"webgl_sandbox",
 				"webgl_sandbox",
 				"webgl_shader",
 				"webgl_shader",
@@ -385,6 +393,7 @@
 				"webgl_buffergeometry_lines_indexed",
 				"webgl_buffergeometry_lines_indexed",
 				"webgl_buffergeometry_points",
 				"webgl_buffergeometry_points",
 				"webgl_buffergeometry_rawshader",
 				"webgl_buffergeometry_rawshader",
+				"webgl_buffergeometry_selective_draw",
 				"webgl_buffergeometry_uint",
 				"webgl_buffergeometry_uint",
 				"webgl_custom_attributes",
 				"webgl_custom_attributes",
 				"webgl_custom_attributes_lines",
 				"webgl_custom_attributes_lines",
@@ -409,6 +418,7 @@
 				"css3dstereo_periodictable",
 				"css3dstereo_periodictable",
 			],
 			],
 			"misc": [
 			"misc": [
+				"misc_animation_authoring",
 				"misc_animation_keys",
 				"misc_animation_keys",
 				"misc_controls_deviceorientation",
 				"misc_controls_deviceorientation",
 				"misc_controls_fly",
 				"misc_controls_fly",
@@ -450,7 +460,6 @@
 				"canvas_lines_dashed",
 				"canvas_lines_dashed",
 				"canvas_lines_sphere",
 				"canvas_lines_sphere",
 				"canvas_materials",
 				"canvas_materials",
-				"canvas_materials_depth",
 				"canvas_materials_normal",
 				"canvas_materials_normal",
 				"canvas_materials_reflection",
 				"canvas_materials_reflection",
 				"canvas_materials_video",
 				"canvas_materials_video",

+ 33 - 57
examples/js/AnimationClipCreator.js

@@ -1,7 +1,7 @@
 /**
 /**
  *
  *
  * Creator of typical test AnimationClips / KeyframeTracks
  * Creator of typical test AnimationClips / KeyframeTracks
- * 
+ *
  * @author Ben Houston / http://clara.io/
  * @author Ben Houston / http://clara.io/
  * @author David Sarno / http://lighthaus.us/
  * @author David Sarno / http://lighthaus.us/
  */
  */
@@ -11,73 +11,64 @@ THREE.AnimationClipCreator = function() {
 
 
 THREE.AnimationClipCreator.CreateRotationAnimation = function( period, axis ) {
 THREE.AnimationClipCreator.CreateRotationAnimation = function( period, axis ) {
 
 
-	var keys = [];
-	keys.push( { time: 0, value: 0 } );
-	keys.push( { time: period, value: 360 } );
+	var times = [ 0, period ], values = [ 0, 360 ];
 
 
 	axis = axis || 'x';
 	axis = axis || 'x';
 	var trackName = '.rotation[' + axis + ']';
 	var trackName = '.rotation[' + axis + ']';
 
 
-	var track = new THREE.NumberKeyframeTrack( trackName, keys );
+	var track = new THREE.NumberKeyframeTrack( trackName, times, values );
 
 
-	var clip = new THREE.AnimationClip( 'rotate.x', 10, [ track ] );
-	//console.log( 'rotateClip', clip );
+	return new THREE.AnimationClip( null, period, [ track ] );
 
 
-	return clip;
 };
 };
 
 
 THREE.AnimationClipCreator.CreateScaleAxisAnimation = function( period, axis ) {
 THREE.AnimationClipCreator.CreateScaleAxisAnimation = function( period, axis ) {
 
 
-	var keys = [];
-	keys.push( { time: 0, value: 0 } );
-	keys.push( { time: period, value: 360 } );
+	var times = [ 0, period ], values = [ 0, 1 ];
 
 
 	axis = axis || 'x';
 	axis = axis || 'x';
 	var trackName = '.scale[' + axis + ']';
 	var trackName = '.scale[' + axis + ']';
 
 
-	var track = new THREE.NumberKeyframeTrack( trackName, keys );
+	var track = new THREE.NumberKeyframeTrack( trackName, times, values );
 
 
-	var clip = new THREE.AnimationClip( 'scale.x', 10, [ track ] );
-	//console.log( 'scaleClip', clip );
+	return new THREE.AnimationClip( null, period, [ track ] );
 
 
-	return clip;
 };
 };
 
 
 THREE.AnimationClipCreator.CreateShakeAnimation = function( duration, shakeScale ) {
 THREE.AnimationClipCreator.CreateShakeAnimation = function( duration, shakeScale ) {
 
 
-	var keys = [];
+	var times = [], values = [], tmp = new THREE.Vector3();
 
 
 	for( var i = 0; i < duration * 10; i ++ ) {
 	for( var i = 0; i < duration * 10; i ++ ) {
 
 
-		keys.push( { 
-			time: ( i / 10.0 ),
-			value: new THREE.Vector3( Math.random() * 2.0 - 1.0, Math.random() * 2.0 - 1.0, Math.random() * 2.0 - 1.0 ).multiply( shakeScale )
-		} );
+		times.push( i / 10 );
+
+		tmp.set( Math.random() * 2.0 - 1.0, Math.random() * 2.0 - 1.0, Math.random() * 2.0 - 1.0 ).
+			multiply( shakeScale ).
+			toArray( values, values.length );
 
 
 	}
 	}
 
 
 	var trackName = '.position';
 	var trackName = '.position';
 
 
-	var track = new THREE.VectorKeyframeTrack( trackName, keys );
+	var track = new THREE.VectorKeyframeTrack( trackName, times, values );
 
 
-	var clip = new THREE.AnimationClip( 'shake' + duration, duration, [ track ] );
-	//console.log( 'shakeClip', clip );
+	return new THREE.AnimationClip( null, duration, [ track ] );
 
 
-	return clip;
 };
 };
 
 
 
 
 THREE.AnimationClipCreator.CreatePulsationAnimation = function( duration, pulseScale ) {
 THREE.AnimationClipCreator.CreatePulsationAnimation = function( duration, pulseScale ) {
 
 
-	var keys = [];
+	var times = [], values = [], tmp = new THREE.Vector3();
 
 
 	for( var i = 0; i < duration * 10; i ++ ) {
 	for( var i = 0; i < duration * 10; i ++ ) {
 
 
+		times.push( i / 10 );
+
 		var scaleFactor = Math.random() * pulseScale;
 		var scaleFactor = Math.random() * pulseScale;
-		keys.push( {
-			time: ( i / 10.0 ),
-			value: new THREE.Vector3( scaleFactor, scaleFactor, scaleFactor )
-		} );
+		tmp.set( scaleFactor, scaleFactor, scaleFactor ).
+			toArray( values, values.length );
 
 
 	}
 	}
 
 
@@ -85,55 +76,40 @@ THREE.AnimationClipCreator.CreatePulsationAnimation = function( duration, pulseS
 
 
 	var track = new THREE.VectorKeyframeTrack( trackName, keys );
 	var track = new THREE.VectorKeyframeTrack( trackName, keys );
 
 
-	var clip = new THREE.AnimationClip( 'scale' + duration, duration, [ track ] );
-	//console.log( 'scaleClip', clip );
+	return new THREE.AnimationClip( null, duration, [ track ] );
 
 
-	return clip;
 };
 };
 
 
 
 
 THREE.AnimationClipCreator.CreateVisibilityAnimation = function( duration ) {
 THREE.AnimationClipCreator.CreateVisibilityAnimation = function( duration ) {
 
 
-	var keys = [];
-	keys.push( {
-		time: 0,
-		value: true
-	} );
-	keys.push( {
-		time: duration - 1,
-		value: false
-	} );
-	keys.push( {
-		time: duration,
-		value: true
-	} );
+	var times = [ 0, duration / 2, duration ], values = [ true, false, true ];
 
 
 	var trackName = '.visible';
 	var trackName = '.visible';
 
 
-	var track = new THREE.BooleanKeyframeTrack( trackName, keys );
+	var track = new THREE.BooleanKeyframeTrack( trackName, times, values );
 
 
-	var clip = new THREE.AnimationClip( 'visible' + duration, duration, [ track ] );
-	//console.log( 'scaleClip', clip );
+	return new THREE.AnimationClip( null, duration, [ track ] );
 
 
-	return clip;
 };
 };
 
 
 
 
 THREE.AnimationClipCreator.CreateMaterialColorAnimation = function( duration, colors, loop ) {
 THREE.AnimationClipCreator.CreateMaterialColorAnimation = function( duration, colors, loop ) {
 
 
-	var timeStep = duration / colors.length;
-	var keys = [];
+	var times = [], values = [],
+		timeStep = duration / colors.length;
+
 	for( var i = 0; i <= colors.length; i ++ ) {
 	for( var i = 0; i <= colors.length; i ++ ) {
-		keys.push( { time: i * timeStep, value: colors[ i % colors.length ] } );
+
+		timees.push( i * timeStep );
+		values.push( colors[ i % colors.length ] );
+
 	}
 	}
 
 
 	var trackName = '.material[0].color';
 	var trackName = '.material[0].color';
 
 
-	var track = new THREE.ColorKeyframeTrack( trackName, keys );
+	var track = new THREE.ColorKeyframeTrack( trackName, times, values );
 
 
-	var clip = new THREE.AnimationClip( 'colorDiffuse', 10, [ track ] );
-	//console.log( 'diffuseClip', clip );
+	return new THREE.AnimationClip( null, duration, [ track ] );
 
 
-	return clip;
 };
 };
-

+ 23 - 28
examples/js/BlendCharacter.js

@@ -4,7 +4,6 @@
 
 
 THREE.BlendCharacter = function () {
 THREE.BlendCharacter = function () {
 
 
-	this.animations = {};
 	this.weightSchedule = [];
 	this.weightSchedule = [];
 	this.warpSchedule = [];
 	this.warpSchedule = [];
 
 
@@ -20,13 +19,13 @@ THREE.BlendCharacter = function () {
 
 
 			THREE.SkinnedMesh.call( scope, geometry, originalMaterial );
 			THREE.SkinnedMesh.call( scope, geometry, originalMaterial );
 
 
-			scope.mixer = new THREE.AnimationMixer( scope );
+			var mixer = new THREE.AnimationMixer( scope );
+			scope.mixer = mixer;
 
 
-			// Create the animations		
+			// Create the animations
 			for ( var i = 0; i < geometry.animations.length; ++ i ) {
 			for ( var i = 0; i < geometry.animations.length; ++ i ) {
 
 
-				var animName = geometry.animations[ i ].name;
-				scope.animations[ animName ] = geometry.animations[ i ];
+				mixer.clipAction( geometry.animations[ i ] );
 
 
 			}
 			}
 
 
@@ -45,49 +44,45 @@ THREE.BlendCharacter = function () {
 
 
 	this.play = function( animName, weight ) {
 	this.play = function( animName, weight ) {
 
 
-		this.mixer.removeAllActions();
-		
-		this.mixer.play( new THREE.AnimationAction( this.animations[ animName ] ) );
-
+		//console.log("play('%s', %f)", animName, weight);
+		return this.mixer.clipAction( animName ).
+				setEffectiveWeight( weight ).play();
 	};
 	};
 
 
 	this.crossfade = function( fromAnimName, toAnimName, duration ) {
 	this.crossfade = function( fromAnimName, toAnimName, duration ) {
 
 
-		this.mixer.removeAllActions();
- 
-		var fromAction = new THREE.AnimationAction( this.animations[ fromAnimName ] );
-		var toAction = new THREE.AnimationAction( this.animations[ toAnimName ] );
+		this.mixer.stopAllAction();
 
 
-		this.mixer.play( fromAction );
-		this.mixer.play( toAction );
+		var fromAction = this.play( fromAnimName, 1 );
+		var toAction = this.play( toAnimName, 1 );
 
 
-		this.mixer.crossFade( fromAction, toAction, duration, false );
+		fromAction.crossFadeTo( toAction, duration, false );
 
 
 	};
 	};
 
 
 	this.warp = function( fromAnimName, toAnimName, duration ) {
 	this.warp = function( fromAnimName, toAnimName, duration ) {
 
 
-		this.mixer.removeAllActions();
-
-		var fromAction = new THREE.AnimationAction( this.animations[ fromAnimName ] );
-		var toAction = new THREE.AnimationAction( this.animations[ toAnimName ] );
+		this.mixer.stopAllAction();
 
 
-		this.mixer.play( fromAction );
-		this.mixer.play( toAction );
+		var fromAction = this.play( fromAnimName, 1 );
+		var toAction = this.play( toAnimName, 1 );
 
 
-		this.mixer.crossFade( fromAction, toAction, duration, true );
+		fromAction.crossFadeTo( toAction, duration, true );
 
 
 	};
 	};
 
 
 	this.applyWeight = function( animName, weight ) {
 	this.applyWeight = function( animName, weight ) {
 
 
-		var action = this.mixer.findActionByName( animName );
-		if( action ) {
-			action.weight = weight;
-		}
+		this.mixer.clipAction( animName ).setEffectiveWeight( weight );
 
 
 	};
 	};
 
 
+	this.getWeight = function( animName ) {
+
+		return this.mixer.clipAction( animName ).getEffectiveWeight();
+
+	}
+
 	this.pauseAll = function() {
 	this.pauseAll = function() {
 
 
 		this.mixer.timeScale = 0;
 		this.mixer.timeScale = 0;
@@ -103,7 +98,7 @@ THREE.BlendCharacter = function () {
 
 
 	this.stopAll = function() {
 	this.stopAll = function() {
 
 
-		this.mixer.removeAllActions();
+		this.mixer.stopAllAction();
 
 
 	};
 	};
 
 

+ 3 - 12
examples/js/BlendCharacterGui.js

@@ -40,18 +40,9 @@ function BlendCharacterGui( blendMesh ) {
 
 
 	this.update = function( time ) {
 	this.update = function( time ) {
 
 
-		var getWeight = function( actionName ) {
-			for( var i = 0; i < blendMesh.mixer.actions.length; i ++ ) {
-				var action = blendMesh.mixer.actions[i];
-				if( action.clip.name === actionName ) {
-					return action.getWeightAt( time );	
-				}
-			}
-			return 0;
-		}
-		controls[ 'idle' ] = getWeight( 'idle' );
-		controls[ 'walk' ] = getWeight( 'walk' );
-		controls[ 'run' ] = getWeight( 'run' );
+		controls[ 'idle' ] = blendMesh.getWeight( 'idle' );
+		controls[ 'walk' ] = blendMesh.getWeight( 'walk' );
+		controls[ 'run' ] = blendMesh.getWeight( 'run' );
 
 
 	};
 	};
 
 

+ 26 - 12
examples/js/MD2Character.js

@@ -33,7 +33,6 @@ THREE.MD2Character = function () {
 
 
 		var weaponsTextures = [];
 		var weaponsTextures = [];
 		for ( var i = 0; i < config.weapons.length; i ++ ) weaponsTextures[ i ] = config.weapons[ i ][ 1 ];
 		for ( var i = 0; i < config.weapons.length; i ++ ) weaponsTextures[ i ] = config.weapons[ i ][ 1 ];
-
 		// SKINS
 		// SKINS
 
 
 		this.skinsBody = loadTextures( config.baseUrl + "skins/", config.skins );
 		this.skinsBody = loadTextures( config.baseUrl + "skins/", config.skins );
@@ -81,6 +80,21 @@ THREE.MD2Character = function () {
 				scope.weapons[ index ] = mesh;
 				scope.weapons[ index ] = mesh;
 				scope.meshWeapon = mesh;
 				scope.meshWeapon = mesh;
 
 
+
+				// the animation system requires unique names, so append the
+				// uuid of the source geometry:
+
+				var geometry = mesh.geometry,
+					animations = geometry.animations;
+
+				for ( var i = 0, n = animations.length; i !== n; ++ i ) {
+
+					var animation = animations[ i ];
+					animation.name += geometry.uuid;
+
+				}
+
+
 				checkLoadingComplete();
 				checkLoadingComplete();
 
 
 			}
 			}
@@ -154,17 +168,15 @@ THREE.MD2Character = function () {
 		if ( this.meshBody ) {
 		if ( this.meshBody ) {
 
 
 			if( this.meshBody.activeAction ) {
 			if( this.meshBody.activeAction ) {
-				scope.mixer.removeAction( this.meshBody.activeAction );
+				this.meshBody.activeAction.stop();
 				this.meshBody.activeAction = null;
 				this.meshBody.activeAction = null;
 			}
 			}
 
 
 			var clip = THREE.AnimationClip.findByName( this.meshBody.geometry.animations, clipName );
 			var clip = THREE.AnimationClip.findByName( this.meshBody.geometry.animations, clipName );
 			if( clip ) {
 			if( clip ) {
 
 
-				var action = new THREE.AnimationAction( clip, this.mixer.time ).setLocalRoot( this.meshBody );
-				scope.mixer.addAction( action );
-
-				this.meshBody.activeAction = action;
+				this.meshBody.activeAction =
+						this.mixer.clipAction( clip, this.meshBody ).play();
 
 
 			}
 			}
 
 
@@ -183,17 +195,19 @@ THREE.MD2Character = function () {
 		if ( scope.meshWeapon ) {
 		if ( scope.meshWeapon ) {
 
 
 			if( this.meshWeapon.activeAction ) {
 			if( this.meshWeapon.activeAction ) {
-				scope.mixer.removeAction( this.meshWeapon.activeAction );
+				this.meshWeapon.activeAction.stop();
 				this.meshWeapon.activeAction = null;
 				this.meshWeapon.activeAction = null;
 			}
 			}
 
 
-			var clip = THREE.AnimationClip.findByName( this.meshWeapon.geometry.animations, clipName );
-			if( clip ) {
+			var geometry = this.meshWeapon.geometry,
+				animations = geometry.animations;
 
 
-				var action = new THREE.AnimationAction( clip ).syncWith( this.meshBody.activeAction ).setLocalRoot( this.meshWeapon );
-				scope.mixer.addAction( action );
+			var clip = THREE.AnimationClip.findByName( animations, clipName + geometry.uuid );
+			if( clip ) {
 
 
-				this.meshWeapon.activeAction = action;
+				this.meshWeapon.activeAction =
+						this.mixer.clipAction( clip, this.meshWeapon ).
+							syncWith( this.meshBody.activeAction ).play();
 
 
 			}
 			}
 
 

+ 3 - 4
examples/js/MorphAnimMesh.js

@@ -31,7 +31,7 @@ THREE.MorphAnimMesh.prototype.playAnimation = function ( label, fps ) {
 
 
 	if( this.activeAction ) {
 	if( this.activeAction ) {
 
 
-		this.mixer.removeAction( this.activeAction );
+		this.activeAction.stop();
 		this.activeAction = null;
 		this.activeAction = null;
 		
 		
 	}
 	}
@@ -40,10 +40,9 @@ THREE.MorphAnimMesh.prototype.playAnimation = function ( label, fps ) {
 
 
 	if ( clip ) {
 	if ( clip ) {
 
 
-		var action = new THREE.AnimationAction( clip );
+		var action = this.mixer.clipAction( clip );
 		action.timeScale = ( clip.tracks.length * fps ) / clip.duration;
 		action.timeScale = ( clip.tracks.length * fps ) / clip.duration;
-		this.mixer.addAction( action );
-		this.activeAction = action;
+		this.activeAction = action.play();
 
 
 	} else {
 	} else {
 
 

+ 25 - 110
examples/js/ShaderSkin.js

@@ -80,35 +80,11 @@ THREE.ShaderSkin = {
 			"varying vec3 vNormal;",
 			"varying vec3 vNormal;",
 			"varying vec2 vUv;",
 			"varying vec2 vUv;",
 
 
-			"uniform vec3 ambientLightColor;",
-
-			"#if MAX_DIR_LIGHTS > 0",
-
-				"uniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];",
-				"uniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];",
-
-			"#endif",
-
-			"#if MAX_HEMI_LIGHTS > 0",
-
-				"uniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];",
-				"uniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];",
-				"uniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];",
-
-			"#endif",
-
-			"#if MAX_POINT_LIGHTS > 0",
-
-				"uniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];",
-				"uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];",
-				"uniform float pointLightDistance[ MAX_POINT_LIGHTS ];",
-				"uniform float pointLightDecay[ MAX_POINT_LIGHTS ];",
-
-			"#endif",
-
 			"varying vec3 vViewPosition;",
 			"varying vec3 vViewPosition;",
 
 
 			THREE.ShaderChunk[ "common" ],
 			THREE.ShaderChunk[ "common" ],
+			THREE.ShaderChunk[ "bsdfs" ],
+			THREE.ShaderChunk[ "lights_pars" ],
 			THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
 			THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
 			THREE.ShaderChunk[ "fog_pars_fragment" ],
 			THREE.ShaderChunk[ "fog_pars_fragment" ],
 			THREE.ShaderChunk[ "bumpmap_pars_fragment" ],
 			THREE.ShaderChunk[ "bumpmap_pars_fragment" ],
@@ -160,6 +136,7 @@ THREE.ShaderSkin = {
 
 
 				"vec3 outgoingLight = vec3( 0.0 );",	// outgoing light does not have an alpha, the surface does
 				"vec3 outgoingLight = vec3( 0.0 );",	// outgoing light does not have an alpha, the surface does
 				"vec4 diffuseColor = vec4( diffuse, opacity );",
 				"vec4 diffuseColor = vec4( diffuse, opacity );",
+				"vec3 shadowMask = vec3( 1.0 );",
 
 
 				"vec4 colDiffuse = texture2D( tDiffuse, vUv );",
 				"vec4 colDiffuse = texture2D( tDiffuse, vUv );",
 				"colDiffuse.rgb *= colDiffuse.rgb;",
 				"colDiffuse.rgb *= colDiffuse.rgb;",
@@ -197,9 +174,9 @@ THREE.ShaderSkin = {
 
 
 					"for ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {",
 					"for ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {",
 
 
-						"vec3 lVector = pointLightPosition[ i ] + vViewPosition.xyz;",
+						"vec3 lVector = pointLights[ i ].position + vViewPosition.xyz;",
 
 
-						"float attenuation = calcLightAttenuation( length( lVector ), pointLightDistance[ i ], pointLightDecay[i] );",
+						"float attenuation = calcLightAttenuation( length( lVector ), pointLights[ i ].distance, pointLights[ i ].decay );",
 
 
 						"lVector = normalize( lVector );",
 						"lVector = normalize( lVector );",
 
 
@@ -209,8 +186,8 @@ THREE.ShaderSkin = {
 
 
 						"float pointSpecularWeight = KS_Skin_Specular( normal, lVector, viewerDirection, uRoughness, uSpecularBrightness );",
 						"float pointSpecularWeight = KS_Skin_Specular( normal, lVector, viewerDirection, uRoughness, uSpecularBrightness );",
 
 
-						"totalDiffuseLight += pointLightColor[ i ] * ( pointDiffuseWeight * attenuation );",
-						"totalSpecularLight += pointLightColor[ i ] * specular * ( pointSpecularWeight * specularStrength * attenuation );",
+						"totalDiffuseLight += pointLight[ i ].color * ( pointDiffuseWeight * attenuation );",
+						"totalSpecularLight += pointLight[ i ].color * specular * ( pointSpecularWeight * specularStrength * attenuation );",
 
 
 					"}",
 					"}",
 
 
@@ -222,7 +199,7 @@ THREE.ShaderSkin = {
 
 
 					"for( int i = 0; i < MAX_DIR_LIGHTS; i++ ) {",
 					"for( int i = 0; i < MAX_DIR_LIGHTS; i++ ) {",
 
 
-						"vec3 dirVector = directionalLightDirection[ i ];",
+						"vec3 dirVector = directionalLights[ i ].direction;",
 
 
 						"float dirDiffuseWeightFull = max( dot( normal, dirVector ), 0.0 );",
 						"float dirDiffuseWeightFull = max( dot( normal, dirVector ), 0.0 );",
 						"float dirDiffuseWeightHalf = max( 0.5 * dot( normal, dirVector ) + 0.5, 0.0 );",
 						"float dirDiffuseWeightHalf = max( 0.5 * dot( normal, dirVector ) + 0.5, 0.0 );",
@@ -230,8 +207,8 @@ THREE.ShaderSkin = {
 
 
 						"float dirSpecularWeight = KS_Skin_Specular( normal, dirVector, viewerDirection, uRoughness, uSpecularBrightness );",
 						"float dirSpecularWeight = KS_Skin_Specular( normal, dirVector, viewerDirection, uRoughness, uSpecularBrightness );",
 
 
-						"totalDiffuseLight += directionalLightColor[ i ] * dirDiffuseWeight;",
-						"totalSpecularLight += directionalLightColor[ i ] * ( dirSpecularWeight * specularStrength );",
+						"totalDiffuseLight += directionalLights[ i ].color * dirDiffuseWeight;",
+						"totalSpecularLight += directionalLights[ i ].color * ( dirSpecularWeight * specularStrength );",
 
 
 					"}",
 					"}",
 
 
@@ -268,9 +245,13 @@ THREE.ShaderSkin = {
 
 
 				"#endif",
 				"#endif",
 
 
+				THREE.ShaderChunk[ "shadowmap_fragment" ],
+
+				"totalDiffuseLight *= shadowMask;",
+				"totalSpecularLight *= shadowMask;",
+
 				"outgoingLight += diffuseColor.xyz * ( totalDiffuseLight + ambientLightColor * diffuse ) + totalSpecularLight;",
 				"outgoingLight += diffuseColor.xyz * ( totalDiffuseLight + ambientLightColor * diffuse ) + totalSpecularLight;",
 
 
-				THREE.ShaderChunk[ "shadowmap_fragment" ],
 				THREE.ShaderChunk[ "linear_to_gamma_fragment" ],
 				THREE.ShaderChunk[ "linear_to_gamma_fragment" ],
 				THREE.ShaderChunk[ "fog_fragment" ],
 				THREE.ShaderChunk[ "fog_fragment" ],
 
 
@@ -389,21 +370,10 @@ THREE.ShaderSkin = {
 			"varying vec3 vNormal;",
 			"varying vec3 vNormal;",
 			"varying vec2 vUv;",
 			"varying vec2 vUv;",
 
 
-			"uniform vec3 ambientLightColor;",
-
-			"#if MAX_DIR_LIGHTS > 0",
-				"uniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];",
-				"uniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];",
-			"#endif",
-
-			"#if MAX_POINT_LIGHTS > 0",
-				"uniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];",
-				"varying vec4 vPointLight[ MAX_POINT_LIGHTS ];",
-			"#endif",
-
 			"varying vec3 vViewPosition;",
 			"varying vec3 vViewPosition;",
 
 
 			THREE.ShaderChunk[ "common" ],
 			THREE.ShaderChunk[ "common" ],
+			THREE.ShaderChunk[ "lights_pars" ],
 			THREE.ShaderChunk[ "fog_pars_fragment" ],
 			THREE.ShaderChunk[ "fog_pars_fragment" ],
 
 
 			"float fresnelReflectance( vec3 H, vec3 V, float F0 ) {",
 			"float fresnelReflectance( vec3 H, vec3 V, float F0 ) {",
@@ -485,12 +455,12 @@ THREE.ShaderSkin = {
 				"#if MAX_POINT_LIGHTS > 0",
 				"#if MAX_POINT_LIGHTS > 0",
 
 
 					"for ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {",
 					"for ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {",
-
-						"vec3 pointVector = normalize( vPointLight[ i ].xyz );",
-						"float attenuation = vPointLight[ i ].w;",
-
+						
+						"vec3 pointVector = normalize( pointLights[ i ].direction );",
+						"float attenuation = calcLightAttenuation( length( lVector ), pointLights[ i ].distance, pointLights[ i ].decay );",
+				
 						"float pointDiffuseWeight = max( dot( normal, pointVector ), 0.0 );",
 						"float pointDiffuseWeight = max( dot( normal, pointVector ), 0.0 );",
-
+				
 						"totalDiffuseLight += pointLightColor[ i ] * ( pointDiffuseWeight * attenuation );",
 						"totalDiffuseLight += pointLightColor[ i ] * ( pointDiffuseWeight * attenuation );",
 
 
 						"if ( passID == 1 ) {",
 						"if ( passID == 1 ) {",
@@ -511,17 +481,18 @@ THREE.ShaderSkin = {
 
 
 					"for( int i = 0; i < MAX_DIR_LIGHTS; i++ ) {",
 					"for( int i = 0; i < MAX_DIR_LIGHTS; i++ ) {",
 
 
-						"vec3 dirVector = directionalLightDirection[ i ];",
+						"vec3 dirVector = directionalLights[ i ].direction;",
 
 
 						"float dirDiffuseWeight = max( dot( normal, dirVector ), 0.0 );",
 						"float dirDiffuseWeight = max( dot( normal, dirVector ), 0.0 );",
 
 
-						"totalDiffuseLight += directionalLightColor[ i ] * dirDiffuseWeight;",
+					
+						"totalDiffuseLight += directionalLights[ i ].color * dirDiffuseWeight;",
 
 
 						"if ( passID == 1 ) {",
 						"if ( passID == 1 ) {",
 
 
 							"float dirSpecularWeight = KS_Skin_Specular( normal, dirVector, viewerDirection, uRoughness, uSpecularBrightness );",
 							"float dirSpecularWeight = KS_Skin_Specular( normal, dirVector, viewerDirection, uRoughness, uSpecularBrightness );",
 
 
-							"totalSpecularLight += directionalLightColor[ i ] * mSpecular.xyz * dirSpecularWeight;",
+							"totalSpecularLight += directionalLights[ i ].color * mSpecular.xyz * dirSpecularWeight;",
 
 
 						"}",
 						"}",
 
 
@@ -602,16 +573,6 @@ THREE.ShaderSkin = {
 			"varying vec3 vNormal;",
 			"varying vec3 vNormal;",
 			"varying vec2 vUv;",
 			"varying vec2 vUv;",
 
 
-			"#if MAX_POINT_LIGHTS > 0",
-
-				"uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];",
-				"uniform float pointLightDistance[ MAX_POINT_LIGHTS ];",
-				"uniform float pointLightDecay[ MAX_POINT_LIGHTS ];",
-
-				"varying vec4 vPointLight[ MAX_POINT_LIGHTS ];",
-
-			"#endif",
-
 			"varying vec3 vViewPosition;",
 			"varying vec3 vViewPosition;",
 
 
 			THREE.ShaderChunk[ "common" ],
 			THREE.ShaderChunk[ "common" ],
@@ -628,24 +589,6 @@ THREE.ShaderSkin = {
 
 
 				"vUv = uv;",
 				"vUv = uv;",
 
 
-				// point lights
-
-				"#if MAX_POINT_LIGHTS > 0",
-
-					"for( int i = 0; i < MAX_POINT_LIGHTS; i++ ) {",
-
-						"vec3 lVector = pointLightPosition[ i ] - vViewPosition;",
-
-						"float attenuation = calcLightAttenuation( length( lVector ), pointLightDistance[ i ], pointLightDecay[i] );",
-
-						"lVector = normalize( lVector );",
-
-						"vPointLight[ i ] = vec4( lVector, attenuation );",
-
-					"}",
-
-				"#endif",
-
 				// displacement mapping
 				// displacement mapping
 
 
 				"#ifdef VERTEX_TEXTURES",
 				"#ifdef VERTEX_TEXTURES",
@@ -670,16 +613,6 @@ THREE.ShaderSkin = {
 			"varying vec3 vNormal;",
 			"varying vec3 vNormal;",
 			"varying vec2 vUv;",
 			"varying vec2 vUv;",
 
 
-			"#if MAX_POINT_LIGHTS > 0",
-
-				"uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];",
-				"uniform float pointLightDistance[ MAX_POINT_LIGHTS ];",
-				"uniform float pointLightDecay[ MAX_POINT_LIGHTS ];",
-
-				"varying vec4 vPointLight[ MAX_POINT_LIGHTS ];",
-
-			"#endif",
-
 			"varying vec3 vViewPosition;",
 			"varying vec3 vViewPosition;",
 
 
 			THREE.ShaderChunk[ "common" ],
 			THREE.ShaderChunk[ "common" ],
@@ -696,24 +629,6 @@ THREE.ShaderSkin = {
 
 
 				"vUv = uv;",
 				"vUv = uv;",
 
 
-				// point lights
-
-				"#if MAX_POINT_LIGHTS > 0",
-
-					"for( int i = 0; i < MAX_POINT_LIGHTS; i++ ) {",
-
-						"vec3 lVector = pointLightPosition[ i ] - vViewPosition;",
-
-						"float attenuation = calcLightAttenuation( length( lVector ), pointLightDistance[ i ], pointLightDecay[i] );",
-
-						"lVector = normalize( lVector );",
-
-						"vPointLight[ i ] = vec4( lVector, attenuation );",
-
-					"}",
-
-				"#endif",
-
 				"gl_Position = vec4( uv.x * 2.0 - 1.0, uv.y * 2.0 - 1.0, 0.0, 1.0 );",
 				"gl_Position = vec4( uv.x * 2.0 - 1.0, uv.y * 2.0 - 1.0, 0.0, 1.0 );",
 
 
 			"}"
 			"}"

+ 11 - 33
examples/js/ShaderTerrain.js

@@ -86,33 +86,11 @@ THREE.ShaderTerrain = {
 
 
 			"uniform vec3 ambientLightColor;",
 			"uniform vec3 ambientLightColor;",
 
 
-			"#if MAX_DIR_LIGHTS > 0",
-
-				"uniform vec3 directionalLightColor[ MAX_DIR_LIGHTS ];",
-				"uniform vec3 directionalLightDirection[ MAX_DIR_LIGHTS ];",
-
-			"#endif",
-
-			"#if MAX_HEMI_LIGHTS > 0",
-
-				"uniform vec3 hemisphereLightSkyColor[ MAX_HEMI_LIGHTS ];",
-				"uniform vec3 hemisphereLightGroundColor[ MAX_HEMI_LIGHTS ];",
-				"uniform vec3 hemisphereLightDirection[ MAX_HEMI_LIGHTS ];",
-
-			"#endif",
-
-			"#if MAX_POINT_LIGHTS > 0",
-
-				"uniform vec3 pointLightColor[ MAX_POINT_LIGHTS ];",
-				"uniform vec3 pointLightPosition[ MAX_POINT_LIGHTS ];",
-				"uniform float pointLightDistance[ MAX_POINT_LIGHTS ];",
-				"uniform float pointLightDecay[ MAX_POINT_LIGHTS ];",
-
-			"#endif",
-
 			"varying vec3 vViewPosition;",
 			"varying vec3 vViewPosition;",
 
 
 			THREE.ShaderChunk[ "common" ],
 			THREE.ShaderChunk[ "common" ],
+			THREE.ShaderChunk[ "bsdfs" ],
+			THREE.ShaderChunk[ "lights_pars" ],
 			THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
 			THREE.ShaderChunk[ "shadowmap_pars_fragment" ],
 			THREE.ShaderChunk[ "fog_pars_fragment" ],
 			THREE.ShaderChunk[ "fog_pars_fragment" ],
 
 
@@ -168,9 +146,9 @@ THREE.ShaderTerrain = {
 
 
 					"for ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {",
 					"for ( int i = 0; i < MAX_POINT_LIGHTS; i ++ ) {",
 
 
-						"vec3 lVector = pointLightPosition[ i ] + vViewPosition.xyz;",
+						"vec3 lVector = pointLights[ i ].position + vViewPosition.xyz;",
 
 
-						"float attenuation = calcLightAttenuation( length( lVector ), pointLightDistance[ i ], pointLightDecay[i] );",
+						"float attenuation = calcLightAttenuation( length( lVector ), pointLights[ i ].distance, pointLights[ i ].decay );",
 
 
 						"lVector = normalize( lVector );",
 						"lVector = normalize( lVector );",
 
 
@@ -181,8 +159,8 @@ THREE.ShaderTerrain = {
 
 
 						"float pointSpecularWeight = specularTex.r * max( pow( pointDotNormalHalf, shininess ), 0.0 );",
 						"float pointSpecularWeight = specularTex.r * max( pow( pointDotNormalHalf, shininess ), 0.0 );",
 
 
-						"totalDiffuseLight += attenuation * pointLightColor[ i ] * pointDiffuseWeight;",
-						"totalSpecularLight += attenuation * pointLightColor[ i ] * specular * pointSpecularWeight * pointDiffuseWeight;",
+						"totalDiffuseLight += attenuation * pointLights[ i ].color * pointDiffuseWeight;",
+						"totalSpecularLight += attenuation * pointLights[ i ].color * specular * pointSpecularWeight * pointDiffuseWeight;",
 
 
 					"}",
 					"}",
 
 
@@ -197,7 +175,7 @@ THREE.ShaderTerrain = {
 
 
 					"for( int i = 0; i < MAX_DIR_LIGHTS; i++ ) {",
 					"for( int i = 0; i < MAX_DIR_LIGHTS; i++ ) {",
 
 
-						"vec3 dirVector = directionalLightDirection[ i ];",
+						"vec3 dirVector = directionalLights[ i ].direction;",
 						"vec3 dirHalfVector = normalize( dirVector + viewPosition );",
 						"vec3 dirHalfVector = normalize( dirVector + viewPosition );",
 
 
 						"float dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );",
 						"float dirDotNormalHalf = max( dot( normal, dirHalfVector ), 0.0 );",
@@ -205,8 +183,8 @@ THREE.ShaderTerrain = {
 
 
 						"float dirSpecularWeight = specularTex.r * max( pow( dirDotNormalHalf, shininess ), 0.0 );",
 						"float dirSpecularWeight = specularTex.r * max( pow( dirDotNormalHalf, shininess ), 0.0 );",
 
 
-						"totalDiffuseLight += directionalLightColor[ i ] * dirDiffuseWeight;",
-						"totalSpecularLight += directionalLightColor[ i ] * specular * dirSpecularWeight * dirDiffuseWeight;",
+						"totalDiffuseLight += directionalLights[ i ].color * dirDiffuseWeight;",
+						"totalSpecularLight += directionalLights[ i ].color * specular * dirSpecularWeight * dirDiffuseWeight;",
 
 
 					"}",
 					"}",
 
 
@@ -228,7 +206,7 @@ THREE.ShaderTerrain = {
 						"float dotProduct = dot( normal, lVector );",
 						"float dotProduct = dot( normal, lVector );",
 						"float hemiDiffuseWeight = 0.5 * dotProduct + 0.5;",
 						"float hemiDiffuseWeight = 0.5 * dotProduct + 0.5;",
 
 
-						"totalDiffuseLight += mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight );",
+						"totalDiffuseLight += mix( hemisphereLights[ i ].groundColor, hemisphereLights[ i ].skyColor, hemiDiffuseWeight );",
 
 
 						// specular (sky light)
 						// specular (sky light)
 
 
@@ -246,7 +224,7 @@ THREE.ShaderTerrain = {
 						"float hemiDotNormalHalfGround = 0.5 * dot( normal, hemiHalfVectorGround ) + 0.5;",
 						"float hemiDotNormalHalfGround = 0.5 * dot( normal, hemiHalfVectorGround ) + 0.5;",
 						"hemiSpecularWeight += specularTex.r * max( pow( hemiDotNormalHalfGround, shininess ), 0.0 );",
 						"hemiSpecularWeight += specularTex.r * max( pow( hemiDotNormalHalfGround, shininess ), 0.0 );",
 
 
-						"totalSpecularLight += specular * mix( hemisphereLightGroundColor[ i ], hemisphereLightSkyColor[ i ], hemiDiffuseWeight ) * hemiSpecularWeight * hemiDiffuseWeight;",
+						"totalSpecularLight += specular * mix( hemisphereLights[ i ].groundColor, hemisphereLights[ i ].skyColor, hemiDiffuseWeight ) * hemiSpecularWeight * hemiDiffuseWeight;",
 
 
 					"}",
 					"}",
 
 

+ 281 - 0
examples/js/TimelinerController.js

@@ -0,0 +1,281 @@
+/**
+ * Controller class for the Timeliner GUI.
+ *
+ * Timeliner GUI library (required to use this class):
+ *
+ * 		./libs/timeliner_gui.min.js
+ *
+ * Source code:
+ *
+ * 		https://github.com/tschw/timeliner_gui
+ * 		https://github.com/zz85/timeliner (fork's origin)
+ *
+ * @author tschw
+ *
+ */
+
+THREE.TimelinerController = function TimelinerController( scene, trackInfo, onUpdate ) {
+
+	this._scene = scene;
+	this._trackInfo = trackInfo;
+
+	this._onUpdate = onUpdate;
+
+	this._mixer = new THREE.AnimationMixer( scene );
+	this._clip = null;
+	this._action = null;
+
+	this._tracks = {};
+	this._propRefs = {};
+	this._channelNames = [];
+
+};
+
+THREE.TimelinerController.prototype = {
+
+	constructor: THREE.TimelinerController,
+
+	init: function( timeliner ) {
+
+		var tracks = [],
+			trackInfo = this._trackInfo;
+
+		for ( var i = 0, n = trackInfo.length; i !== n; ++ i ) {
+
+			var spec = trackInfo[ i ];
+
+			tracks.push( this._addTrack(
+					spec.type, spec.propertyPath,
+					spec.initialValue, spec.interpolation ) );
+		}
+
+		this._clip = new THREE.AnimationClip( 'editclip', 0, tracks );
+		this._action = this._mixer.clipAction( this._clip ).play();
+
+	},
+
+	setDisplayTime: function( time ) {
+
+		this._action.time = time;
+		this._mixer.update( 0 );
+
+		this._onUpdate();
+
+	},
+
+	setDuration: function( duration ) {
+
+		this._clip.duration = duration;
+
+	},
+
+	getChannelNames: function() {
+
+		return this._channelNames;
+
+	},
+
+	getChannelKeyTimes: function( channelName ) {
+
+		return this._tracks[ channelName ].times;
+
+	},
+
+	setKeyframe: function( channelName, time ) {
+
+		var track = this._tracks[ channelName ],
+			times = track.times,
+			index = Timeliner.binarySearch( times, time ),
+			values = track.values,
+			stride = track.getValueSize(),
+			offset = index * stride;
+
+		if ( index < 0 ) {
+
+			// insert new keyframe
+
+			index = ~ index;
+			offset = index * stride;
+
+			var nTimes = times.length + 1,
+				nValues = values.length + stride;
+
+			times[ index ] = time;
+
+			for ( var i = nTimes - 1; i !== index; -- i ) {
+
+				times[ i ] = times[ i - 1 ];
+
+			}
+
+			for ( var i = nValues - 1,
+					e = offset + stride - 1; i !== e; -- i ) {
+
+				values[ i ] = values[ i - stride ];
+
+			}
+
+		}
+
+		this._propRefs[ channelName ].getValue( values, offset );
+
+	},
+
+	delKeyframe: function( channelName, time ) {
+
+		var track = this._tracks[ channelName ],
+			times = track.times,
+			index = Timeliner.binarySearch( times, time );
+
+		// we disallow to remove the keyframe when it is the last one we have,
+		// since the animation system is designed to always produce a defined
+		// state
+
+		if ( times.length > 1 && index >= 0 ) {
+
+			var nTimes = times.length - 1,
+				values = track.values,
+				stride = track.getValueSize(),
+				nValues = values.length - stride;
+
+			// note: no track.getValueSize when array sizes are out of sync
+
+			for ( var i = index; i !== nTimes; ++ i ) {
+
+				times[ i ] = times[ i + 1 ];
+
+			}
+
+			times.pop();
+
+			for ( var offset = index * stride; offset !== nValues; ++ offset ) {
+
+				values[ offset ] = values[ offset + stride ];
+
+			}
+
+			values.length = nValues;
+
+		}
+
+	},
+
+	moveKeyframe: function( channelName, time, delta, moveRemaining ) {
+
+		var track = this._tracks[ channelName ],
+			times = track.times,
+			index = Timeliner.binarySearch( times, time );
+
+		if ( index >= 0 ) {
+
+			var endAt = moveRemaining ? times.length : index + 1,
+				needsSort = times[ index - 1 ] <= time ||
+					! moveRemaining && time >= times[ index + 1 ];
+
+			while ( index !== endAt ) times[ index ++ ] += delta;
+
+			if ( needsSort ) this._sort( track );
+
+		}
+
+	},
+
+	serialize: function() {
+
+		var result = {
+				duration: this._clip.duration,
+				channels: {}
+			},
+
+			names = this._channelNames,
+			tracks = this._tracks,
+
+			channels = result.channels;
+
+		for ( var i = 0, n = names.length; i !== n; ++ i ) {
+
+			var name = names[ i ],
+				track = tracks[ name ];
+
+			channels[ name ] = {
+
+				times: track.times,
+				values: track.values
+
+			};
+
+		}
+
+		return result;
+
+	},
+
+	deserialize: function( structs ) {
+
+		var names = this._channelNames,
+			tracks = this._tracks,
+
+			channels = structs.channels;
+
+		this.setDuration( structs.duration );
+
+		for ( var i = 0, n = names.length; i !== n; ++ i ) {
+
+			var name = names[ i ],
+				track = tracks[ name ];
+				data = channels[ name ];
+
+			this._setArray( track.times, data.times );
+			this._setArray( track.values, data.values );
+
+		}
+
+		// update display
+		this.setDisplayTime( this._mixer.time );
+
+	},
+
+	_sort: function( track ) {
+
+		var times = track.times,
+			order = THREE.AnimationUtils.getKeyframeOrder( times );
+
+		this._setArray( times,
+				THREE.AnimationUtils.sortedArray( times, 1, order ) );
+
+		var values = track.values,
+			stride = track.getValueSize();
+
+		this._setArray( values,
+				THREE.AnimationUtils.sortedArray( values, stride, order ) );
+
+	},
+
+	_setArray: function( dst, src ) {
+
+		dst.length = 0;
+		dst.push.apply( dst, src );
+
+	},
+
+	_addTrack: function( type, prop, initialValue, interpolation ) {
+
+		var track = new type(
+				prop, [ 0 ], initialValue, interpolation );
+
+		// data must be in JS arrays so it can be resized
+		track.times = Array.prototype.slice.call( track.times );
+		track.values = Array.prototype.slice.call( track.values );
+
+		this._channelNames.push( prop );
+		this._tracks[ prop ] = track;
+
+		// for recording the state:
+		this._propRefs[ prop ] =
+				new THREE.PropertyBinding( this._scene, prop );
+
+		return track;
+
+	}
+
+};

+ 1 - 1
examples/js/UCSCharacter.js

@@ -55,7 +55,7 @@ THREE.UCSCharacter = function() {
 			mesh.castShadow = true;
 			mesh.castShadow = true;
 			mesh.receiveShadow = true;
 			mesh.receiveShadow = true;
 
 
-			scope.mixer.addAction( new THREE.AnimationAction( geometry.animations[0] ).setLocalRoot( mesh ) );
+			scope.mixer.clipAction( geometry.animations[0], mesh ).play();
 			
 			
 			scope.setSkin( 0 );
 			scope.setSkin( 0 );
 			
 			

+ 4 - 4
examples/js/controls/EditorControls.js

@@ -214,7 +214,7 @@ THREE.EditorControls = function ( object, domElement ) {
 		domElement.removeEventListener( 'contextmenu', contextmenu, false );
 		domElement.removeEventListener( 'contextmenu', contextmenu, false );
 		domElement.removeEventListener( 'mousedown', onMouseDown, false );
 		domElement.removeEventListener( 'mousedown', onMouseDown, false );
 		domElement.removeEventListener( 'mousewheel', onMouseWheel, false );
 		domElement.removeEventListener( 'mousewheel', onMouseWheel, false );
-		domElement.removeEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox
+		domElement.removeEventListener( 'MozMousePixelScroll', onMouseWheel, false ); // firefox
 
 
 		domElement.removeEventListener( 'mousemove', onMouseMove, false );
 		domElement.removeEventListener( 'mousemove', onMouseMove, false );
 		domElement.removeEventListener( 'mouseup', onMouseUp, false );
 		domElement.removeEventListener( 'mouseup', onMouseUp, false );
@@ -229,7 +229,7 @@ THREE.EditorControls = function ( object, domElement ) {
 	domElement.addEventListener( 'contextmenu', contextmenu, false );
 	domElement.addEventListener( 'contextmenu', contextmenu, false );
 	domElement.addEventListener( 'mousedown', onMouseDown, false );
 	domElement.addEventListener( 'mousedown', onMouseDown, false );
 	domElement.addEventListener( 'mousewheel', onMouseWheel, false );
 	domElement.addEventListener( 'mousewheel', onMouseWheel, false );
-	domElement.addEventListener( 'DOMMouseScroll', onMouseWheel, false ); // firefox
+	domElement.addEventListener( 'MozMousePixelScroll', onMouseWheel, false ); // firefox
 
 
 	// touch
 	// touch
 
 
@@ -272,7 +272,7 @@ THREE.EditorControls = function ( object, domElement ) {
 		event.preventDefault();
 		event.preventDefault();
 		event.stopPropagation();
 		event.stopPropagation();
 
 
-		var getClosest = function( touch, touches ) {
+		function getClosest( touch, touches ) {
 
 
 			var closest = touches[ 0 ];
 			var closest = touches[ 0 ];
 
 
@@ -284,7 +284,7 @@ THREE.EditorControls = function ( object, domElement ) {
 
 
 			return closest;
 			return closest;
 
 
-		};
+		}
 
 
 		switch ( event.touches.length ) {
 		switch ( event.touches.length ) {
 
 

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