Bläddra i källkod

Merge branch 'editor' of git://github.com/arodic/three.js into dev

Mr.doob 12 år sedan
förälder
incheckning
f260fb8087
39 ändrade filer med 2206 tillägg och 991 borttagningar
  1. 78 22
      editor/index.html
  2. 1052 0
      editor/js/Editor.js
  3. 19 23
      editor/js/Loader.js
  4. 76 125
      editor/js/Menubar.Add.js
  5. 11 3
      editor/js/Menubar.Edit.js
  6. 21 23
      editor/js/Menubar.File.js
  7. 1 1
      editor/js/Menubar.Help.js
  8. 1 1
      editor/js/Menubar.js
  9. 3 3
      editor/js/Sidebar.Animation.js
  10. 10 18
      editor/js/Sidebar.Geometry.CubeGeometry.js
  11. 16 24
      editor/js/Sidebar.Geometry.CylinderGeometry.js
  12. 6 14
      editor/js/Sidebar.Geometry.IcosahedronGeometry.js
  13. 8 16
      editor/js/Sidebar.Geometry.PlaneGeometry.js
  14. 11 19
      editor/js/Sidebar.Geometry.SphereGeometry.js
  15. 9 18
      editor/js/Sidebar.Geometry.TorusGeometry.js
  16. 11 19
      editor/js/Sidebar.Geometry.TorusKnotGeometry.js
  17. 26 24
      editor/js/Sidebar.Geometry.js
  18. 85 30
      editor/js/Sidebar.Material.js
  19. 158 105
      editor/js/Sidebar.Object3D.js
  20. 74 0
      editor/js/Sidebar.Outliner.Geometries.js
  21. 74 0
      editor/js/Sidebar.Outliner.Materials.js
  22. 68 86
      editor/js/Sidebar.Outliner.Scene.js
  23. 74 0
      editor/js/Sidebar.Outliner.Textures.js
  24. 12 0
      editor/js/Sidebar.Outliner.js
  25. 1 1
      editor/js/Sidebar.Renderer.js
  26. 11 0
      editor/js/Sidebar.Selected.js
  27. 4 6
      editor/js/Sidebar.js
  28. 4 4
      editor/js/Toolbar.js
  29. 78 308
      editor/js/Viewport.js
  30. 97 24
      editor/js/libs/ui.js
  31. 1 0
      examples/js/exporters/MaterialExporter.js
  32. 2 0
      examples/js/exporters/ObjectExporter.js
  33. 87 69
      examples/js/loaders/ObjectLoader.js
  34. 1 0
      src/core/Geometry.js
  35. 10 4
      src/core/Object3D.js
  36. 1 0
      src/materials/Material.js
  37. 2 1
      src/scenes/Fog.js
  38. 2 0
      src/scenes/FogExp2.js
  39. 1 0
      src/textures/Texture.js

+ 78 - 22
editor/index.html

@@ -70,7 +70,7 @@
 				overflow: auto;
 			}
 
-				.sidebar .Panel {
+				.sidebar .Text {
 					margin-bottom: 10px;
 				}
 
@@ -111,15 +111,25 @@
 		<script src="js/libs/ui.js"></script>
 		<script src="js/libs/ui.three.js"></script>
 
+		<script src="js/Editor.js"></script>
+
 		<script src="js/Loader.js"></script>
 		<script src="js/Menubar.js"></script>
 		<script src="js/Menubar.File.js"></script>
 		<script src="js/Menubar.Edit.js"></script>
 		<script src="js/Menubar.Add.js"></script>
 		<script src="js/Menubar.Help.js"></script>
+
 		<script src="js/Sidebar.js"></script>
 		<script src="js/Sidebar.Renderer.js"></script>
-		<script src="js/Sidebar.Scene.js"></script>
+
+		<script src="js/Sidebar.Outliner.js"></script>
+		<script src="js/Sidebar.Outliner.Scene.js"></script>
+		<script src="js/Sidebar.Outliner.Geometries.js"></script>
+		<script src="js/Sidebar.Outliner.Materials.js"></script>
+		<script src="js/Sidebar.Outliner.Textures.js"></script>
+
+		<script src="js/Sidebar.Selected.js"></script>
 		<script src="js/Sidebar.Object3D.js"></script>
 		<script src="js/Sidebar.Geometry.js"></script>
 		<script src="js/Sidebar.Animation.js"></script>
@@ -131,6 +141,7 @@
 		<script src="js/Sidebar.Geometry.TorusGeometry.js"></script>
 		<script src="js/Sidebar.Geometry.TorusKnotGeometry.js"></script>
 		<script src="js/Sidebar.Material.js"></script>
+
 		<script src="js/Toolbar.js"></script>
 		<script src="js/Viewport.js"></script>
 
@@ -145,53 +156,66 @@
 
 				// actions
 
-				cloneSelectedObject: new SIGNALS.Signal(),
-				removeSelectedObject: new SIGNALS.Signal(),
+				setRenderer: new SIGNALS.Signal(),
+				undo: new SIGNALS.Signal(),
+				redo: new SIGNALS.Signal(),
 				playAnimations: new SIGNALS.Signal(),
+				setTransformMode: new SIGNALS.Signal(),
 
-				// notifications
+				// editor events
 
-				transformModeChanged: new SIGNALS.Signal(),
-				snapChanged: new SIGNALS.Signal(),
-				rendererChanged: new SIGNALS.Signal(),
-				sceneAdded: new SIGNALS.Signal(),
-				sceneChanged: new SIGNALS.Signal(),
 				objectAdded: new SIGNALS.Signal(),
-				objectSelected: new SIGNALS.Signal(),
+				geometryAdded: new SIGNALS.Signal(),
+				materialAdded: new SIGNALS.Signal(),
+				textureAdded: new SIGNALS.Signal(),
+
 				objectChanged: new SIGNALS.Signal(),
+				geometryChanged: new SIGNALS.Signal(),
 				materialChanged: new SIGNALS.Signal(),
+				textureChanged: new SIGNALS.Signal(),
+				sceneChanged: new SIGNALS.Signal(),
+				fogChanged: new SIGNALS.Signal(),
+				
+				objectDeleted: new SIGNALS.Signal(),
+				geometryDeleted: new SIGNALS.Signal(),
+				materialDeleted: new SIGNALS.Signal(),
+				textureDeleted: new SIGNALS.Signal(),
+				selected: new SIGNALS.Signal(),
+
+				/// viewport events
+								
+				snapChanged: new SIGNALS.Signal(),
 				clearColorChanged: new SIGNALS.Signal(),
-				fogTypeChanged: new SIGNALS.Signal(),
-				fogColorChanged: new SIGNALS.Signal(),
-				fogParametersChanged: new SIGNALS.Signal(),
 				windowResize: new SIGNALS.Signal()
 
 			};
 
 			//
 
-			var loader = new Loader( signals );
+			var editor = new Editor();
+
+			var loader = new Loader( editor, signals );
 
-			var viewport = new Viewport( signals );
+			var viewport = new Viewport( editor, signals );
 			viewport.setTop( '32px' );
 			viewport.setLeft( '0px' );
 			viewport.setRight( '300px' );
 			viewport.setBottom( '32px' );
 			document.body.appendChild( viewport.dom );
 
-			var toolbar = new Toolbar( signals );
+			var toolbar = new Toolbar( editor, signals );
 			toolbar.setBottom( '0px' );
 			toolbar.setLeft( '0px' );
 			toolbar.setRight( '300px' );
 			toolbar.setHeight( '32px' );
 			document.body.appendChild( toolbar.dom );
 
-			var menubar = new Menubar( signals );
+			var menubar = new Menubar( editor, signals );
 			menubar.setWidth( '100%' );
 			menubar.setHeight( '32px' );
 			document.body.appendChild( menubar.dom );
 
-			var sidebar = new Sidebar( signals );
+			var sidebar = new Sidebar( editor, signals );
 			sidebar.setRight( '0px' );
 			sidebar.setTop( '32px' );
 			sidebar.setBottom( '0px' );
@@ -204,9 +228,34 @@
 				switch ( event.keyCode ) {
 
 					case 46: // delete
+					case 8: // backspace
+
+						event.preventDefault();
+						editor.delete();
+						break;
+
+					case 37: // left arrow
+
+						event.preventDefault();
+						editor.pickWalk( 'left' );
+						break;
+
+					case 38: // up arrow
+
+						event.preventDefault();
+						editor.pickWalk( 'up' );
+						break;
+
+					case 39: // right arrow
 
-						signals.removeSelectedObject.dispatch();
+						event.preventDefault();
+						editor.pickWalk( 'right' );
+						break;
 
+					case 40: // down arrow
+ 
+						event.preventDefault();
+						editor.pickWalk( 'down' );
 						break;
 
 					}
@@ -221,9 +270,16 @@
 
 			window.addEventListener( 'resize', onWindowResize, false );
 
-			onWindowResize();
+			document.addEventListener( "DOMContentLoaded", function(){
+    		
+    		onWindowResize();
+
+				loader.loadLocalStorage();
+
+    	}, false );
+
 
-			loader.loadLocalStorage();
+			
 
 		</script>
 	</body>

+ 1052 - 0
editor/js/Editor.js

@@ -0,0 +1,1052 @@
+var Editor = function ( scene ) {
+
+  this.geometries = {}; 
+  this.materials = {}; 
+  this.textures = {}; 
+  this.objects = {};
+  this.selected = {};
+  this.helpers = {};
+
+  this.scene = new THREE.Scene();
+  this.scene.name = ( scene && scene.name ) ? scene.name : 'Scene';
+  this.addObject( this.scene );
+  
+  this.sceneHelpers = new THREE.Scene();
+
+  this.defaultMaterial = new THREE.MeshPhongMaterial();
+  this.defaultMaterial.name = 'Default Material';
+  this.addMaterial( this.defaultMaterial );
+
+}
+
+Editor.prototype = {
+
+  // Assets
+
+  setScene: function( scene ) {
+
+    this.deleteAll(); // WARNING! deletes everything 
+
+    if ( scene ) {
+
+      this.scene.name = scene.name ? scene.name : 'Scene';
+      this.scene.userData = JSON.parse( JSON.stringify( scene.userData ) );
+
+      if ( scene.children.length ) this.addObject( scene.children );
+
+    }
+
+    signals.sceneChanged.dispatch( this.scene );
+
+    return this.scene 
+
+  },
+
+  createObject: function( type, parameters, material ) {
+
+    type = type ? type.toLowerCase() : null;
+
+    var object;
+    var geometry;
+
+    material = material ? material : this.defaultMaterial;
+
+    parameters = parameters ? parameters : {};
+
+    var name = parameters.name ? parameters.name : null;
+    var width = parameters.width ? parameters.width : null;
+    var height = parameters.height ? parameters.height : null;
+    var depth = parameters.depth ? parameters.depth : null;
+    var widthSegments = parameters.widthSegments ? parameters.widthSegments : null;
+    var heightSegments = parameters.heightSegments ? parameters.heightSegments : null;
+    var depthSegments = parameters.depthSegments ? parameters.depthSegments : null;
+    var radialSegments = parameters.radialSegments ? parameters.radialSegments : null;
+    var tubularSegments = parameters.tubularSegments ? parameters.tubularSegments : null;
+    var radius = parameters.radius ? parameters.radius : null;
+    var radiusTop = parameters.radiusTop ? parameters.radiusTop : null;
+    var radiusBottom = parameters.radiusBottom ? parameters.radiusBottom : null;
+    var phiStart = parameters.phiStart ? parameters.phiStart : null;
+    var phiLength = parameters.phiLength ? parameters.phiLength : null;
+    var thetaStart = parameters.thetaStart ? parameters.thetaStart : null;
+    var thetaLength = parameters.thetaLength ? parameters.thetaLength : null;
+    var tube = parameters.tube ? parameters.tube : null;
+    var arc = parameters.arc ? parameters.arc : null;
+    var detail = parameters.detail ? parameters.detail : null;
+    var p = parameters.p ? parameters.p : null;
+    var q = parameters.q ? parameters.q : null;
+    var heightScale = parameters.heightScale ? parameters.heightScale : null;
+    var openEnded = parameters.openEnded ? parameters.openEnded : false;
+    var color = parameters.color ? parameters.color : null;
+    var intensity = parameters.intensity ? parameters.intensity : null;
+    var distance = parameters.distance ? parameters.distance : null;
+    var angle = parameters.angle ? parameters.angle : null;
+    var exponent = parameters.exponent ? parameters.exponent : null;
+    var skyColor = parameters.skyColor ? parameters.skyColor : null;
+    var groundColor = parameters.groundColor ? parameters.groundColor : null;
+
+    if ( !type ) {
+
+      object = new THREE.Object3D();
+      object.name = parameters.name ? parameters.name : 'Group ' + object.id;
+
+    } else if ( type == 'plane' ) {
+
+      width = width ? width : 200;
+      height = height ? height : 200;
+
+      widthSegments = widthSegments ? widthSegments : 1;
+      heightSegments = heightSegments ? heightSegments : 1;
+
+      geometry = new THREE.PlaneGeometry( width, height, widthSegments, heightSegments );
+      object = new THREE.Mesh( geometry, this.defaultMaterial );
+      object.name = name ? name : 'Plane ' + object.id;
+      geometry.name = object.name + ' geometry';
+
+      object.rotation.x = - Math.PI/2;
+
+    } else if ( type == 'cube' ) {
+
+      width = width ? width : 100;
+      height = height ? height : 100;
+      depth = depth ? depth : 100;
+
+      widthSegments = widthSegments ? widthSegments : 1;
+      heightSegments = heightSegments ? heightSegments : 1;
+      depthSegments = depthSegments ? depthSegments : 1;
+
+      geometry = new THREE.CubeGeometry( width, height, depth, widthSegments, heightSegments, depthSegments );
+      object = new THREE.Mesh( geometry, this.defaultMaterial );
+      object.name = name ? name : 'Cube ' + object.id;
+      geometry.name = object.name + ' geometry';
+
+    } else if ( type == 'cylinder' ) {
+
+      radiusTop = radiusTop ? radiusTop : 20;
+      radiusBottom = radiusBottom ? radiusBottom : 20;
+      height = height ? height : 100;
+      radialSegments = radialSegments ? radialSegments : 8;
+      heightSegments = heightSegments ? heightSegments : 1;
+      openEnded = openEnded ? openEnded : false;
+
+      geometry = new THREE.CylinderGeometry( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded );
+      object = new THREE.Mesh( geometry, this.defaultMaterial );
+      object.name = name ? name : 'Cylinder ' + object.id;
+      geometry.name = object.name + ' geometry';
+
+    } else if ( type == 'sphere' ) {
+
+      radius = radius ? radius : 75;
+      widthSegments = widthSegments ? widthSegments : 32;
+      heightSegments = heightSegments ? heightSegments : 16;
+      widthSegments = widthSegments ? widthSegments : 32;
+      heightSegments = heightSegments ? heightSegments : 16;
+      phiStart = phiStart ? phiStart : null;
+      phiLength = phiLength ? phiLength : null;
+      thetaStart = thetaStart ? thetaStart : null;
+      thetaLength = thetaLength ? thetaLength : null;
+
+      geometry = new THREE.SphereGeometry( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength );
+      object = new THREE.Mesh( geometry, this.defaultMaterial );
+      object.name = name ? name : 'Sphere ' + object.id;
+      geometry.name = object.name + ' geometry';
+
+    } else if ( type == 'icosahedron' ) {
+
+      radius = radius ? radius : 75;
+      detail = detail ? detail : 2;
+
+      geometry = new THREE.IcosahedronGeometry ( radius, detail );
+      object = new THREE.Mesh( geometry, this.defaultMaterial );
+      object.name = name ? name : 'Icosahedron ' + object.id;
+      geometry.name = object.name + ' geometry';
+
+    } else if ( type == 'torus' ) {
+
+      radius = radius ? radius : 100;
+      tube = tube ? tube : 40;
+      radialSegments = radialSegments ? radialSegments : 8;
+      tubularSegments = tubularSegments ? tubularSegments : 6;
+      arc = arc ? arc : Math.PI * 2;
+
+      geometry = new THREE.TorusGeometry( radius, tube, radialSegments, tubularSegments, arc );
+      object = new THREE.Mesh( geometry, this.defaultMaterial );
+      object.name = name ? name : 'Torus ' + object.id;
+      geometry.name = object.name + ' geometry';
+
+    } else if ( type == 'torusknot' ) {
+
+      radius = radius ? radius : 100;
+      tube = tube ? tube : 40;
+      radialSegments = radialSegments ? radialSegments : 64;
+      tubularSegments = tubularSegments ? tubularSegments : 8;
+      p = p ? p : 2;
+      q = q ? q : 3;
+      heightScale = heightScale ? heightScale : 1;
+
+      geometry = new THREE.TorusKnotGeometry( radius, tube, radialSegments, tubularSegments, p, q, heightScale );
+      object = new THREE.Mesh( geometry, this.defaultMaterial );
+      object.name = name ? name : 'TorusKnot ' + object.id;
+      geometry.name = object.name + ' geometry';
+
+    } else if ( type == 'pointlight' ) {
+
+      color = color ? color : 0xffffff;
+      intensity = intensity ? intensity : 1;
+      distance = distance ? distance : 0;
+
+      var object = new THREE.PointLight( color, intensity, distance );
+      object.name = name ? name : 'PointLight ' + object.id;
+
+    } else if ( type == 'spotlight' ) {
+
+      color = color ? color : 0xffffff;
+      intensity = intensity ? intensity : 1;
+      distance = distance ? distance : 0;
+      angle = angle ? angle : Math.PI * 0.1;
+      exponent = exponent ? exponent : 10;
+
+      var object = new THREE.SpotLight( color, intensity, distance, angle, exponent );
+      object.name =  name ? name : 'SpotLight ' + object.id;
+      object.target.name = object.name + ' Target';
+
+      object.position.set( 0, 1, 0 ).multiplyScalar( 200 );
+
+    } else if ( type == 'directionallight' ) {
+
+      color = color ? color : 0xffffff;
+      intensity = intensity ? intensity : 1;
+
+      var object = new THREE.DirectionalLight( color, intensity );
+      object.name = name ? name : 'DirectionalLight ' + object.id;
+      object.target.name = object.name + ' Target';
+
+      object.position.set( 1, 1, 1 ).multiplyScalar( 200 );
+
+    } else if ( type == 'hemispherelight' ) {
+
+      skyColor = skyColor ? skyColor : 0x00aaff;
+      groundColor = groundColor ? groundColor : 0xffaa00;
+      intensity = intensity ? intensity : 1;
+
+      var object = new THREE.HemisphereLight( skyColor, groundColor, intensity );
+      object.name = name ? name : 'HemisphereLight ' + object.id;
+
+      object.position.set( 1, 1, 1 ).multiplyScalar( 200 );
+
+    } else if ( type == 'ambientlight' ) {
+
+      color = color ? color : 0x222222;
+
+      var object = new THREE.AmbientLight( color );
+      object.name = name ? name : 'AmbientLight ' + object.id;
+
+    }
+
+    if ( object ) this.addObject( object );
+
+    return object;
+
+  },
+
+  createMaterial: function( type, parameters ) {
+
+    var material;
+    
+    type = type ? type.toLowerCase() : 'phong';
+
+    parameters = parameters ? parameters : {};
+
+    var name = parameters.name ? parameters.name : null;
+
+    // TODO: implement types
+
+    if ( type == 'phong' ) {
+
+      material = new THREE.MeshPhongMaterial( parameters );
+      material.name = name ? name : 'Phong Material ' + material.id;
+
+    } else if ( type == 'lambert' ) {
+
+      material = new THREE.MeshLambertMaterial( parameters );
+      material.name = name ? name : 'Lambert Material ' + material.id;
+
+    } else if ( type == 'normal' ) {
+
+      material = new THREE.MeshNormalMaterial( parameters );
+      material.name = name ? name : 'Normal Material ' + material.id;
+
+    } else if ( type == 'basic' ) {
+
+      material = new THREE.MeshBasicMaterial( parameters );
+      material.name = name ? name : 'Basic Material ' + material.id;
+
+    }
+
+    if ( material ) this.addMaterial( material );
+    return material;
+
+  },
+
+  createTexture: function( image, parameters ) {
+
+    image = image ? image : '../examples/textures/ash_uvgrid01.jpg';
+
+    parameters = parameters ? parameters : {};
+
+    // TODO: implement parameters
+
+    var texture = THREE.ImageUtils.loadTexture( image );
+    texture.name = parameters.name ? parameters.name : 'Texture ' + texture.id;
+
+    this.addTexture( texture );
+    return texture;
+
+  },
+
+  addObject: function( list, parent ) {
+
+    list = ( list instanceof Array ) ? [].concat( list ) : [ list ];
+
+    parent = parent ? parent : this.scene;
+
+    for ( var i in list ) {
+
+      if ( !list[ i ].uuid ) list[ i ].uuid = this.uuid();
+      this.objects[ list[ i ].uuid ] = list[ i ];
+      this.addHelper( list[ i ] );
+
+      if ( list[ i ].target ) {
+
+        if ( !list[ i ].target.uuid ) list[ i ].target.uuid = this.uuid();
+        this.objects[ list[ i ].target.uuid ] = list[ i ].target;
+
+      }
+
+      if ( list[ i ].material ) this.addMaterial( list[ i ].material );
+      if ( list[ i ].geometry ) this.addGeometry( list[ i ].geometry );
+
+      if ( parent != list[ i ] ) {
+
+        // Add object to the scene
+
+        parent.add( list[ i ] );
+
+        if ( list[ i ] instanceof THREE.Light ) this.updateMaterials();
+
+        signals.objectAdded.dispatch( list[ i ] );
+
+        // Continue adding children
+
+        if ( list[ i ].children && list[ i ].children.length ) {
+
+          this.addObject( list[ i ].children, list[ i ] );
+
+        }
+
+      }
+
+    }
+
+    signals.sceneChanged.dispatch( this.scene );
+
+  },
+
+  addHelper: function( object ) {
+
+    if ( object instanceof THREE.PointLight ) {
+
+      this.helpers[ object.uuid ] = new THREE.PointLightHelper( object, 10 );
+      this.sceneHelpers.add( this.helpers[ object.uuid ] );
+      this.helpers[ object.uuid ].lightSphere.uuid = object.uuid;
+
+    } else if ( object instanceof THREE.DirectionalLight ) {
+
+      this.helpers[ object.uuid ] = new THREE.DirectionalLightHelper( object, 10 );
+      this.sceneHelpers.add( this.helpers[ object.uuid ] );
+      this.helpers[ object.uuid ].lightSphere.uuid = object.uuid;
+
+    } else if ( object instanceof THREE.SpotLight ) {
+
+      this.helpers[ object.uuid ] = new THREE.SpotLightHelper( object, 10 );
+      this.sceneHelpers.add( this.helpers[ object.uuid ] );
+      this.helpers[ object.uuid ].lightSphere.uuid = object.uuid;
+
+    } else if ( object instanceof THREE.HemisphereLight ) {
+
+      this.helpers[ object.uuid ] = new THREE.HemisphereLightHelper( object, 10 );
+      this.sceneHelpers.add( this.helpers[ object.uuid ] );
+      this.helpers[ object.uuid ].lightSphere.uuid = object.uuid;
+
+    }
+
+  },
+
+  deleteHelper: function( object ) {
+
+    if ( this.helpers[ object.uuid ] ) {
+
+      this.helpers[ object.uuid ].parent.remove( this.helpers[ object.uuid ] );
+      delete this.helpers[ object.uuid ];
+
+    }
+
+  },
+
+  addGeometry: function( geometry ) {
+
+    if (!geometry.uuid) geometry.uuid = this.uuid();
+    this.geometries[ geometry.uuid ] = geometry;
+
+    signals.geometryAdded.dispatch( geometry );
+
+  },
+
+  addMaterial: function( material ) {
+
+    if ( material.name == 'Default Material' ) {
+      this.delete( this.defaultMaterial );
+      this.defaultMaterial = material;
+    }
+
+    if (!material.uuid) material.uuid = this.uuid();
+    this.materials[ material.uuid ] = material;
+
+    signals.materialAdded.dispatch( material );
+
+  },
+
+  addTexture: function( texture ) {
+
+    if (!texture.uuid) texture.uuid = this.uuid();
+    this.textures[ texture.uuid ] = texture;
+
+    signals.textureAdded.dispatch( texture );
+
+  },
+
+  // Selection
+
+  select: function( list, additive ) {
+
+    //TODO toggle
+
+    list = ( list instanceof Array ) ? list : [ list ];
+
+    if ( !additive ) this.selected = {};
+
+    for ( var i in list ) {
+
+      this.selected[ list[ i ].uuid ] = list[ i ];
+
+    }
+
+    signals.selected.dispatch( this.selected );
+
+  },
+
+  selectByUuid: function( uuid, additive ) {
+
+    var list = this.list();
+
+    if ( !additive ) this.selected = {};
+
+    for ( var i in list ) {
+
+      if ( list[ i ].uuid == uuid ) this.select( list[ i ], true );
+
+    }
+
+  },
+
+  selectByName: function( name, type, additive ) {
+
+    type = type ? type : "all";
+
+    this.select( this.listByName( name, type ), additive );
+
+  },
+
+  selectAll: function( type, additive ) {
+
+    type = type ? type : "all";
+
+    this.select( this.listByName( '*', type ), additive );
+
+  },
+
+  deselect: function( list ) {
+
+    list = ( list instanceof Array ) ? list : [ list ];
+
+    for ( var i in list ) {
+
+      if ( this.selected[ list[ i ].uuid ] ) delete this.selected[ list[ i ].uuid ];
+
+    }
+
+    signals.selected.dispatch( this.selected );
+
+  },
+
+  deselectByUuid: function( uuid ) {
+
+    if ( this.selected[ uuid ] ) delete this.selected[ uuid ];
+
+  },
+
+  deselectByName: function( name, type ) {
+
+    type = type ? type : "all";
+
+    this.deselect( this.listByName( name, type ) );
+
+  },
+
+  deselectAll: function( type ) {
+
+    type = type ? type : "all";
+
+    this.deselect( this.list( "all" ) );
+
+  },
+
+  pickWalk: function( direction ) {
+
+    direction = direction.toLowerCase();
+
+    var selection = this.listSelected();
+    var newSelection = [];
+
+    if ( direction === 'up' ) {
+
+      for ( var i in selection ) {
+
+        if ( selection[ i ].parent )
+          newSelection.push( selection[ i ].parent );
+        
+        else newSelection.push( selection[ i ] );
+
+      }
+
+    } else if ( direction === 'down' ) {
+
+      for ( var i in selection ) {
+
+        if ( selection[ i ].children && selection[ i ].children.length )
+          newSelection.push( selection[ i ].children[0] );
+        
+        else newSelection.push( selection[ i ] );
+
+      }
+
+    } else if ( direction === 'left' || direction === 'right' ) {
+
+      for ( var i in selection ) {
+
+        var siblings = null;
+        var index = null;
+        var newIndex = null;
+
+        if ( selection[ i ].parent ) {
+
+          siblings = selection[ i ].parent.children;
+          index = selection[ i ].parent.children.indexOf( selection[ i ] );
+          newIndex = index;
+
+          if ( siblings.length > 1 && direction === 'left' )
+            newIndex = ( index + siblings.length + 1 ) % siblings.length;
+
+          else if ( siblings.length > 1 && direction === 'right' )
+            newIndex = ( index + siblings.length - 1 ) % siblings.length;
+          
+          newSelection.push( siblings[ newIndex ] );
+
+        } else {
+
+          newSelection.push( selection[ i ] );
+
+        }
+
+      }
+
+    }
+
+    if ( newSelection.length ) this.select( newSelection );
+
+  },
+
+  // List
+
+  list: function( type ) {
+
+    type = type ? type : "all";
+
+    var list = this.listByName( '*', type );
+
+    return list;
+
+  },
+
+  listSelected: function( type ) {
+
+    var list = this.listByName( '*', 'selected' );
+
+    if ( type ) {
+
+      var typeList = this.listByName( '*', type );
+
+      var list = list.filter(function(n) {
+        if(typeList.indexOf(n) == -1) return false;
+        return true;
+      });
+
+    } 
+
+    return list;
+
+  },
+
+  listByName: function( name, type ) {
+
+    type = type ? type.toLowerCase() : "all";
+
+    var scope = this;
+    var list = [];
+
+    function listFromMap( map, name ) {
+
+      for ( var uuid in map ) {
+
+        if ( scope.regexMatch( map[ uuid ].name, name ) ) {
+
+          list.push( map[ uuid ] );
+
+        }
+
+
+      }
+
+    }
+    
+    if ( type == 'all' || type == 'object' ) {
+
+      listFromMap( this.objects, name );
+
+    }
+
+    if ( type == 'all' || type == 'geometry' ) {
+
+      listFromMap( this.geometries, name );
+
+    }
+
+    if ( type == 'all' || type == 'material' ) {
+
+      listFromMap( this.materials, name );
+
+    }
+
+    if ( type == 'all' || type == 'texture' ) {
+
+      listFromMap( this.textures, name );
+
+    }
+
+    if ( type == 'all' || type == 'selected' ) {
+
+      listFromMap( this.selected, name );
+
+    }
+
+    return list;
+
+  },
+
+  // Delete
+
+  delete: function( list ) {
+
+    list = list ? list : this.list( 'selected' );
+
+    list = ( list instanceof Array ) ? list : [ list ];
+
+    this.deselect( list );
+
+    var deletedObjects = {};
+
+    for ( var i in list ) {
+
+      // TODO: handle helpers
+      
+      if ( this.objects[ list[ i ].uuid ] && list[ i ] != this.scene ) {
+
+        delete this.objects[ list[ i ].uuid ];
+        this.deleteHelper( list[ i ] );
+
+        deletedObjects[ list[ i ].uuid ] = list[ i ];
+
+        if ( list[ i ] instanceof THREE.Light ) this.updateMaterials();
+
+        signals.objectDeleted.dispatch();
+
+        if ( list[ i ].children.length ) this.delete( list[ i ].children );
+      
+      }
+
+      if ( this.geometries[ list[ i ].uuid ] ) {
+
+        delete this.geometries[ list[ i ].uuid ];
+        signals.objectDeleted.dispatch();
+
+      } 
+
+      if ( this.materials[ list[ i ].uuid ] ) {
+      
+        delete this.materials[ list[ i ].uuid ];
+        signals.materialDeleted.dispatch();
+      
+      }
+
+      if ( this.textures[ list[ i ].uuid ] ) {
+
+        delete this.textures[ list[ i ].uuid ];
+        signals.textureDeleted.dispatch();
+
+      }
+
+    } 
+
+    for ( var i in deletedObjects ) {
+
+        if ( deletedObjects[ i ].parent ) {
+        
+          deletedObjects[ i ].parent.remove( deletedObjects[ i ] );
+        
+        }
+
+    }
+
+    delete deletedObjects;
+
+    signals.sceneChanged.dispatch( this.scene );
+
+  },
+
+  deleteByName: function( name, type ) {
+
+    type = type ? type : "all";
+
+    this.delete( this.listByName( name, type ) );
+
+  },
+
+  deleteAll: function( type ) {
+
+    type = type ? type : 'all';
+
+    this.delete( this.listByName( '*', type ) );
+
+  },
+
+  deleteUnused: function( type ) {
+
+    // TODO: test with textures
+
+    type = type ? type.toLowerCase() : 'all';
+
+    var used = {};
+
+    this.scene.traverse( function( object ) {
+
+      used[ object.uuid ] = object; 
+
+      if ( object.geometry ) used[ object.geometry.uuid ] = object.geometry; 
+
+      if ( object.material ) {
+
+        used[ object.material.uuid ] = object.material;
+
+        for ( var i in object.material ){
+
+          if ( object.material[ i ] instanceof THREE.Texture ) {
+
+            used[ object.material[ i ].uuid ] = object.material[ i ];
+
+          }
+
+        }
+
+      }
+
+    } );
+
+    if ( !type || type == 'object' ) {
+      for ( var uuid in this.objects ) {
+        if ( !used[ uuid ] ) this.delete( this.objects[ uuid ] );
+      }
+    }
+
+    if ( !type || type == 'geometry' ) {
+      for ( var uuid in this.geometries ) {
+        if ( !used[ uuid ] ) this.delete( this.geometries[ uuid ] );
+      }
+    }
+
+    if ( !type || type == 'material' ) {
+      for ( var uuid in this.materials ) {
+        if ( !used[ uuid ] ) this.delete( this.materials[ uuid ] );
+      }
+    }
+
+    if ( !type || type == 'texture' ) {
+      for ( var uuid in this.textures ) {
+        if ( !used[ uuid ] ) this.delete( this.textures[ uuid ] );
+      }
+    }
+
+    delete used;
+
+  },
+
+  // Hierarchy
+
+  clone: function( assets, recursive, deep ) {
+
+    // TODO: consider using list
+
+    // TODO: Implement non-recursive and deep
+
+    var assets = assets ? assets : this.selected;
+    // recursive = recursive ? recursive : true;
+    // deep = deep ? deep : false;
+
+    var clones = {};
+
+    for ( var i in assets ){
+
+      if ( this.objects[ i ] ) {
+
+        var clonedObject = this.objects[assets[ i ].uuid ].clone();
+
+        clonedObject.traverse( function( child ) {
+
+          child.name += ' Clone';
+
+        } );
+
+        this.addObject( clonedObject, assets[ i ].parent );
+        clones[ clonedObject.uuid ] = clonedObject;
+
+      }
+
+    }
+      
+    return clones;
+
+  },
+
+  parent: function( list, parent, locked ) {
+
+    //TODO: implement locked
+
+    list = list ? list : this.list( 'selected' );
+
+    list = ( list instanceof Array ) ? list : [ list ];
+
+    parent = parent ? parent : this.scene;
+
+    for ( var i in list ) {
+
+      if ( list[ i ] !== parent && list[ i ] !== this.scene ) {
+
+        parent.add( list[ i ] );
+        signals.objectChanged.dispatch( list[ i ] );
+
+      }
+
+    }
+
+    signals.sceneChanged.dispatch( this.scene );
+
+  },
+  
+  unparent: function( list ) {
+
+    this.parent( list, this.scene );
+
+  },
+
+  group: function( list ) {
+
+    list = list ? list : this.listSelected( 'objects' );
+
+    list = ( list instanceof Array ) ? list : [ list ];
+
+    var parent = ( list.length && list[0].parent ) ? list[0].parent : this.scene;
+
+    var group = this.createObject();
+
+    this.parent( group, parent );
+
+    console.log(group);
+
+    this.parent( list, group );
+
+  },
+
+  updateMaterials: function( list ) {
+
+    list = list ? list : this.list( 'material' );
+
+    list = ( list instanceof Array ) ? list : [ list ];
+
+    for ( var i in list ) {
+
+      list[ i ].needsUpdate = true;
+
+      if ( list[ i ] instanceof THREE.MeshFaceMaterial ) {
+
+        for ( var j in list[ i ].materials ) {
+
+          list[ i ].materials[ j ].needsUpdate = true;
+
+        }
+
+      }
+
+    }
+
+  },
+
+  remakeGeometry: function( geometry, parameters ) {
+
+    // TODO: document
+
+    parameters = parameters ? parameters : {};
+
+    var uuid = geometry.uuid;
+    var name = parameters.name ? parameters.name : geometry.name;
+
+    var width = parameters.width ? parameters.width : null;
+    var height = parameters.height ? parameters.height : null;
+    var depth = parameters.depth ? parameters.depth : null;
+    var widthSegments = parameters.widthSegments ? parameters.widthSegments : null;
+    var heightSegments = parameters.heightSegments ? parameters.heightSegments : null;
+    var depthSegments = parameters.depthSegments ? parameters.depthSegments : null;
+    var radialSegments = parameters.radialSegments ? parameters.radialSegments : null;
+    var tubularSegments = parameters.tubularSegments ? parameters.tubularSegments : null;
+    var radius = parameters.radius ? parameters.radius : null;
+    var radiusTop = parameters.radiusTop ? parameters.radiusTop : null;
+    var radiusBottom = parameters.radiusBottom ? parameters.radiusBottom : null;
+    var phiStart = parameters.phiStart ? parameters.phiStart : null;
+    var phiLength = parameters.phiLength ? parameters.phiLength : null;
+    var thetaStart = parameters.thetaStart ? parameters.thetaStart : null;
+    var thetaLength = parameters.thetaLength ? parameters.thetaLength : null;
+    var tube = parameters.tube ? parameters.tube : null;
+    var arc = parameters.arc ? parameters.arc : null;
+    var detail = parameters.detail ? parameters.detail : null;
+    var p = parameters.p ? parameters.p : null;
+    var q = parameters.q ? parameters.q : null;
+    var heightScale = parameters.heightScale ? parameters.heightScale : null;
+    var openEnded = parameters.openEnded ? parameters.openEnded : false;
+
+    if ( geometry instanceof THREE.CubeGeometry )
+      editor.geometries[uuid] = new THREE.CubeGeometry( width, height, depth, widthSegments, heightSegments, depthSegments );
+
+    if ( geometry instanceof THREE.CylinderGeometry )
+      editor.geometries[uuid] = new THREE.CylinderGeometry( radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded );
+
+    if ( geometry instanceof THREE.IcosahedronGeometry )
+      editor.geometries[uuid] = new THREE.IcosahedronGeometry( radius, detail );
+
+    if ( geometry instanceof THREE.PlaneGeometry )
+      editor.geometries[uuid] = new THREE.PlaneGeometry( width, height, widthSegments, heightSegments );
+
+    if ( geometry instanceof THREE.SphereGeometry )
+      editor.geometries[uuid] = new THREE.SphereGeometry( radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength );
+
+    if ( geometry instanceof THREE.TorusGeometry )
+      editor.geometries[uuid] = new THREE.TorusGeometry( radius, tube, radialSegments, tubularSegments, arc );
+
+    if ( geometry instanceof THREE.TorusKnotGeometry )
+      editor.geometries[uuid] = new THREE.TorusKnotGeometry( radius, tube, radialSegments, tubularSegments, p, q, heightScale );
+
+    editor.geometries[uuid].computeBoundingSphere();
+    editor.geometries[uuid].uuid = uuid;
+    editor.geometries[uuid].name = name;
+
+    for ( var i in editor.objects ) {
+
+      var object = editor.objects[i];
+
+      if ( object.geometry && object.geometry.uuid == uuid ) {
+
+        delete object.__webglInit; // TODO: Remove hack (WebGLRenderer refactoring)
+        object.geometry.dispose();
+
+        object.geometry = editor.geometries[uuid];
+
+        signals.objectChanged.dispatch( object );
+
+      }
+
+    }
+
+  },
+
+  setFog: function( parameters ) {
+
+    var fogType = parameters.fogType ? parameters.fogType : null;
+    var near = parameters.near ? parameters.near : null;
+    var far = parameters.far ? parameters.far : null;
+    var density = parameters.density ? parameters.density : null;
+    var color = parameters.color ? parameters.color : null;
+
+    if ( fogType ) {
+
+      if ( fogType === "None" ) this.scene.fog = null;
+
+      else if ( fogType === "Fog" ) this.scene.fog = new THREE.Fog();
+
+      else if ( fogType === "FogExp2" ) this.scene.fog = new THREE.FogExp2();
+
+    }
+
+    if ( this.scene.fog ) {
+
+      if ( fogType ) this.scene.fog.fogType = fogType;
+      if ( near ) this.scene.fog.near = near;
+      if ( far ) this.scene.fog.far = far;
+      if ( density ) this.scene.fog.density = density;
+      if ( color ) this.scene.fog.color.setHex( color );
+
+    }
+
+    this.updateMaterials();
+    signals.fogChanged.dispatch( this.scene.fog );
+
+  },
+
+  regexMatch: function( name, filter ) {
+
+    name = name.toLowerCase();
+    filter = '^' + filter.toLowerCase().replace(/\*/g, '.*').replace(/\?/g, '.') + '$';
+
+    var regex = new RegExp(filter);
+    return regex.test( name );
+
+  },
+
+  uuid: function() {
+
+    // http://note19.com/2007/05/27/javascript-guid-generator/
+
+    function s4() {
+      return Math.floor( ( 1 + Math.random() ) * 0x10000 ).toString( 16 ).substring( 1 );
+    };
+
+    return s4() + s4() + '-' + s4() + '-' + s4() + '-' + s4() + '-' + s4() + s4() + s4();
+
+  }
+
+}

+ 19 - 23
editor/js/Loader.js

@@ -1,7 +1,7 @@
 
-var Loader = function ( signals ) {
+var Loader = function ( editor, signals ) {
 
-	scope = this;
+	var scope = this;
 
 	var sceneExporter = new THREE.ObjectExporter();
 
@@ -28,18 +28,20 @@ var Loader = function ( signals ) {
 
 	var timeout;
 
-	signals.sceneChanged.add( function ( scene ) {
+	signals.objectChanged.add( function () {
 
 		clearTimeout( timeout );
 
 		timeout = setTimeout( function () {
 
-			scope.saveLocalStorage( scene );
+			console.log( "Saving to localStorage." );
+			scope.saveLocalStorage( editor.scene );
 
 		}, 3000 );
 
 	} );
 
+
 	this.loadLocalStorage = function () {
 
 		if ( localStorage.threejsEditor !== undefined ) {
@@ -49,7 +51,7 @@ var Loader = function ( signals ) {
 				var loader = new THREE.ObjectLoader();
 				var scene = loader.parse( JSON.parse( localStorage.threejsEditor ) );
 
-				signals.sceneAdded.dispatch( scene );
+				editor.setScene( scene );
 
 			} catch ( e ) {
 
@@ -103,7 +105,7 @@ var Loader = function ( signals ) {
 			var mesh = new THREE.Mesh( geometry, material );
 			mesh.name = filename;
 
-			signals.objectAdded.dispatch( mesh );
+			editor.addObject( mesh );
 
 		} else if ( data.metadata.type === 'object' ) {
 
@@ -112,11 +114,11 @@ var Loader = function ( signals ) {
 
 			if ( result instanceof THREE.Scene ) {
 
-				signals.sceneAdded.dispatch( result );
+				editor.addObject( result );
 
 			} else {
 
-				signals.objectAdded.dispatch( result );
+				editor.addObject( result );
 
 			}
 
@@ -127,13 +129,7 @@ var Loader = function ( signals ) {
 			var loader = new THREE.SceneLoader();
 			loader.parse( data, function ( result ) {
 
-				var scene = result.scene;
-
-				while ( scene.children.length > 0 ) {
-
-					signals.objectAdded.dispatch( scene.children[ 0 ] );
-
-				}
+				editor.addObject( result.scene );
 
 			}, '' );
 
@@ -166,7 +162,7 @@ var Loader = function ( signals ) {
 						var mesh = new THREE.Mesh( geometry, material );
 						mesh.name = filename;
 
-						signals.objectAdded.dispatch( mesh );
+						editor.addObject( mesh );
 
 					} );
 
@@ -190,7 +186,7 @@ var Loader = function ( signals ) {
 
 						collada.scene.name = filename;
 
-						signals.objectAdded.dispatch( collada.scene );
+						editor.addObject( collada.scene );
 
 					} );
 
@@ -262,7 +258,7 @@ var Loader = function ( signals ) {
 					var object = new THREE.OBJLoader().parse( contents );
 					object.name = filename;
 
-					signals.objectAdded.dispatch( object );
+					editor.addObject( object );
 
 				}, false );
 				reader.readAsText( file );
@@ -287,7 +283,7 @@ var Loader = function ( signals ) {
 					var mesh = new THREE.Mesh( geometry, material );
 					mesh.name = filename;
 
-					signals.objectAdded.dispatch( mesh );
+					editor.addObject( mesh );
 
 				}, false );
 				reader.readAsText( file );
@@ -310,7 +306,7 @@ var Loader = function ( signals ) {
 					var mesh = new THREE.Mesh( geometry, material );
 					mesh.name = filename;
 
-					signals.objectAdded.dispatch( mesh );
+					editor.addObject( mesh );
 
 				}, false );
 
@@ -339,7 +335,7 @@ var Loader = function ( signals ) {
 
 					var mesh = new THREE.Mesh( geometry, material );
 
-					signals.objectAdded.dispatch( mesh );
+					editor.addObject( mesh );
 
 				}, false );
 				reader.readAsBinaryString( file );
@@ -363,7 +359,7 @@ var Loader = function ( signals ) {
 					var mesh = new THREE.Mesh( geometry, material );
 					mesh.name = filename;
 
-					signals.objectAdded.dispatch( mesh );
+					editor.addObject( mesh );
 
 				}, false );
 				reader.readAsText( file );
@@ -379,7 +375,7 @@ var Loader = function ( signals ) {
 
 					var result = new THREE.VRMLLoader().parse( contents );
 
-					signals.sceneAdded.dispatch( result );
+					editor.addObject( result );
 
 				}, false );
 				reader.readAsText( file );

+ 76 - 125
editor/js/Menubar.Add.js

@@ -4,7 +4,7 @@ Menubar.Add = function ( signals ) {
 	container.setClass( 'menu' );
 	container.onMouseOver( function () { options.setDisplay( 'block' ) } );
 	container.onMouseOut( function () { options.setDisplay( 'none' ) } );
-	container.onClick( function () { options.setDisplay( 'block' ) } );
+	container.onClick( function () { options.setDisplay( 'none' ) } );
 
 	var title = new UI.Panel();
 	title.setTextContent( 'Add' ).setColor( '#666' );
@@ -26,19 +26,7 @@ Menubar.Add = function ( signals ) {
 	option.setTextContent( 'Plane' );
 	option.onClick( function () {
 
-		var width = 200;
-		var height = 200;
-
-		var widthSegments = 1;
-		var heightSegments = 1;
-
-		var geometry = new THREE.PlaneGeometry( width, height, widthSegments, heightSegments );
-		var mesh = new THREE.Mesh( geometry, createDummyMaterial( geometry ) );
-		mesh.name = 'Plane ' + mesh.id;
-
-		mesh.rotation.x = - Math.PI/2;
-
-		signals.objectAdded.dispatch( mesh );
+		editor.select( editor.createObject( 'Plane' ) );
 
 	} );
 	options.add( option );
@@ -50,20 +38,7 @@ Menubar.Add = function ( signals ) {
 	option.setTextContent( 'Cube' );
 	option.onClick( function () {
 
-		var width = 100;
-		var height = 100;
-		var depth = 100;
-
-		var widthSegments = 1;
-		var heightSegments = 1;
-		var depthSegments = 1;
-
-		var geometry = new THREE.CubeGeometry( width, height, depth, widthSegments, heightSegments, depthSegments );
-		var mesh = new THREE.Mesh( geometry, createDummyMaterial( geometry ) );
-		mesh.name = 'Cube ' + mesh.id;
-
-		signals.objectAdded.dispatch( mesh );
-
+		editor.select( editor.createObject( 'Cube' ) );
 
 	} );
 	options.add( option );
@@ -75,18 +50,7 @@ Menubar.Add = function ( signals ) {
 	option.setTextContent( 'Cylinder' );
 	option.onClick( function () {
 
-		var radiusTop = 20;
-		var radiusBottom = 20;
-		var height = 100;
-		var radiusSegments = 8;
-		var heightSegments = 1;
-		var openEnded = false;
-
-		var geometry = new THREE.CylinderGeometry( radiusTop, radiusBottom, height, radiusSegments, heightSegments, openEnded );
-		var mesh = new THREE.Mesh( geometry, createDummyMaterial( geometry ) );
-		mesh.name = 'Cylinder ' + mesh.id;
-
-		signals.objectAdded.dispatch( mesh );
+		editor.select( editor.createObject( 'Cylinder' ) );
 
 	} );
 	options.add( option );
@@ -98,15 +62,7 @@ Menubar.Add = function ( signals ) {
 	option.setTextContent( 'Sphere' );
 	option.onClick( function () {
 
-		var radius = 75;
-		var widthSegments = 32;
-		var heightSegments = 16;
-
-		var geometry = new THREE.SphereGeometry( radius, widthSegments, heightSegments );
-		var mesh = new THREE.Mesh( geometry, createDummyMaterial( geometry ) );
-		mesh.name = 'Sphere ' + mesh.id;
-
-		signals.objectAdded.dispatch( mesh );
+		editor.select( editor.createObject( 'Sphere' ) );
 
 	} );
 	options.add( option );
@@ -118,14 +74,7 @@ Menubar.Add = function ( signals ) {
 	option.setTextContent( 'Icosahedron' );
 	option.onClick( function () {
 
-		var radius = 75;
-		var detail = 2;
-
-		var geometry = new THREE.IcosahedronGeometry ( radius, detail );
-		var mesh = new THREE.Mesh( geometry, createDummyMaterial( geometry ) );
-		mesh.name = 'Icosahedron ' + mesh.id;
-
-		signals.objectAdded.dispatch( mesh );
+		editor.select( editor.createObject( 'Icosahedron' ) );
 
 	} );
 	options.add( option );
@@ -137,17 +86,7 @@ Menubar.Add = function ( signals ) {
 	option.setTextContent( 'Torus' );
 	option.onClick( function () {
 
-		var radius = 100;
-		var tube = 40;
-		var radialSegments = 8;
-		var tubularSegments = 6;
-		var arc = Math.PI * 2;
-
-		var geometry = new THREE.TorusGeometry( radius, tube, radialSegments, tubularSegments, arc );
-		var mesh = new THREE.Mesh( geometry, createDummyMaterial( geometry ) );
-		mesh.name = 'Torus ' + mesh.id;
-
-		signals.objectAdded.dispatch( mesh );
+		editor.select( editor.createObject( 'Torus' ) );
 
 	} );
 	options.add( option );
@@ -159,19 +98,19 @@ Menubar.Add = function ( signals ) {
 	option.setTextContent( 'TorusKnot' );
 	option.onClick( function () {
 
-		var radius = 100;
-		var tube = 40;
-		var radialSegments = 64;
-		var tubularSegments = 8;
-		var p = 2;
-		var q = 3;
-		var heightScale = 1;
+		editor.select( editor.createObject( 'TorusKnot' ) );
 
-		var geometry = new THREE.TorusKnotGeometry( radius, tube, radialSegments, tubularSegments, p, q, heightScale );
-		var mesh = new THREE.Mesh( geometry, createDummyMaterial( geometry ) );
-		mesh.name = 'TorusKnot ' + mesh.id;
+	} );
+	options.add( option );
 
-		signals.objectAdded.dispatch( mesh );
+	// add group
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Group' );
+	option.onClick( function () {
+
+		editor.select( editor.createObject() );
 
 	} );
 	options.add( option );
@@ -187,14 +126,7 @@ Menubar.Add = function ( signals ) {
 	option.setTextContent( 'Point light' );
 	option.onClick( function () {
 
-		var color = 0xffffff;
-		var intensity = 1;
-		var distance = 0;
-
-		var light = new THREE.PointLight( color, intensity, distance );
-		light.name = 'PointLight ' + light.id;
-
-		signals.objectAdded.dispatch( light );
+		editor.select( editor.createObject( 'PointLight' ) );
 
 	} );
 	options.add( option );
@@ -206,19 +138,7 @@ Menubar.Add = function ( signals ) {
 	option.setTextContent( 'Spot light' );
 	option.onClick( function () {
 
-		var color = 0xffffff;
-		var intensity = 1;
-		var distance = 0;
-		var angle = Math.PI * 0.1;
-		var exponent = 10;
-
-		var light = new THREE.SpotLight( color, intensity, distance, angle, exponent );
-		light.name = 'SpotLight ' + light.id;
-		light.target.name = 'SpotLight ' + light.id + ' Target';
-
-		light.position.set( 0, 1, 0 ).multiplyScalar( 200 );
-
-		signals.objectAdded.dispatch( light );
+		editor.select( editor.createObject( 'SpotLight' ) );
 
 	} );
 	options.add( option );
@@ -230,65 +150,96 @@ Menubar.Add = function ( signals ) {
 	option.setTextContent( 'Directional light' );
 	option.onClick( function () {
 
-		var color = 0xffffff;
-		var intensity = 1;
+		editor.select( editor.createObject( 'DirectionaLight' ) );
 
-		var light = new THREE.DirectionalLight( color, intensity );
-		light.name = 'DirectionalLight ' + light.id;
-		light.target.name = 'DirectionalLight ' + light.id + ' Target';
+	} );
+	options.add( option );
 
-		light.position.set( 1, 1, 1 ).multiplyScalar( 200 );
+	// add hemisphere light
 
-		signals.objectAdded.dispatch( light );
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Hemisphere light' );
+	option.onClick( function () {
+
+		editor.select( editor.createObject( 'HemisphereLight' ) );
 
 	} );
 	options.add( option );
 
-	// add hemisphere light
+	// add ambient light
 
 	var option = new UI.Panel();
 	option.setClass( 'option' );
-	option.setTextContent( 'Hemisphere light' );
+	option.setTextContent( 'Ambient light' );
 	option.onClick( function () {
 
-		var skyColor = 0x00aaff;
-		var groundColor = 0xffaa00;
-		var intensity = 1;
+		editor.select( editor.createObject( 'AmbientLight' ) );
+
+	} );
+	options.add( option );
+
+	//
+
+	options.add( new UI.HorizontalRule() );
 
-		var light = new THREE.HemisphereLight( skyColor, groundColor, intensity );
-		light.name = 'HemisphereLight ' + light.id;
+	// add material
 
-		light.position.set( 1, 1, 1 ).multiplyScalar( 200 );
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Phong material' );
+	option.onClick( function () {
 
-		signals.objectAdded.dispatch( light );
+		editor.select( editor.createMaterial( 'Phong' ) );
 
 	} );
 	options.add( option );
 
-	// add ambient light
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Lambert material' );
+	option.onClick( function () {
+
+		editor.select( editor.createMaterial( 'Lambert' ) );
+
+	} );
+	options.add( option );
 
 	var option = new UI.Panel();
 	option.setClass( 'option' );
-	option.setTextContent( 'Ambient light' );
+	option.setTextContent( 'Normal material' );
 	option.onClick( function () {
 
-		var color = 0x222222;
+		editor.select( editor.createMaterial( 'Normal' ) );
 
-		var light = new THREE.AmbientLight( color );
-		light.name = 'AmbientLight ' + light.id;
+	} );
+	options.add( option );
+
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Basic material' );
+	option.onClick( function () {
 
-		signals.objectAdded.dispatch( light );
+		editor.select( editor.createMaterial( 'Basic' ) );
 
 	} );
 	options.add( option );
 
 	//
 
-	function createDummyMaterial() {
+	options.add( new UI.HorizontalRule() );
 
-		return new THREE.MeshPhongMaterial();
+	// add texture
 
-	};
+	var option = new UI.Panel();
+	option.setClass( 'option' );
+	option.setTextContent( 'Texture' );
+	option.onClick( function () {
+
+		editor.select( editor.createTexture() );
+
+	} );
+	options.add( option );
 
 	return container;
 

+ 11 - 3
editor/js/Menubar.Edit.js

@@ -4,7 +4,7 @@ Menubar.Edit = function ( signals ) {
 	container.setClass( 'menu' );
 	container.onMouseOver( function () { options.setDisplay( 'block' ) } );
 	container.onMouseOut( function () { options.setDisplay( 'none' ) } );
-	container.onClick( function () { options.setDisplay( 'block' ) } );
+	container.onClick( function () { options.setDisplay( 'none' ) } );
 
 	var title = new UI.Panel();
 	title.setTextContent( 'Edit' ).setColor( '#666' );
@@ -24,7 +24,11 @@ Menubar.Edit = function ( signals ) {
 	var option = new UI.Panel();
 	option.setClass( 'option' );
 	option.setTextContent( 'Clone' );
-	option.onClick( function () { signals.cloneSelectedObject.dispatch(); } );
+	option.onClick( function () {
+
+		editor.clone();
+
+	} );
 	options.add( option );
 
 	// delete
@@ -32,7 +36,11 @@ Menubar.Edit = function ( signals ) {
 	var option = new UI.Panel();
 	option.setClass( 'option' );
 	option.setTextContent( 'Delete' );
-	option.onClick( function () { signals.removeSelectedObject.dispatch(); } );
+	option.onClick( function () { 
+
+		editor.delete();
+
+	} );
 	options.add( option );
 
 	//

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

@@ -4,7 +4,7 @@ Menubar.File = function ( signals ) {
 	container.setClass( 'menu' );
 	container.onMouseOver( function () { options.setDisplay( 'block' ) } );
 	container.onMouseOut( function () { options.setDisplay( 'none' ) } );
-	container.onClick( function () { options.setDisplay( 'block' ) } );
+	container.onClick( function () { options.setDisplay( 'none' ) } );
 
 	var title = new UI.Panel();
 	title.setTextContent( 'File' ).setColor( '#666' );
@@ -14,9 +14,6 @@ Menubar.File = function ( signals ) {
 
 	//
 
-	var selectedObject;
-	var scene;
-
 	var options = new UI.Panel();
 	options.setClass( 'options' );
 	options.setDisplay( 'none' );
@@ -47,7 +44,7 @@ Menubar.File = function ( signals ) {
 
 			}
 
-			location.reload();
+			location.replace( location.origin + location.pathname );
 
 		}
 
@@ -120,7 +117,14 @@ Menubar.File = function ( signals ) {
 
 	var exportGeometry = function ( exporterClass ) {
 
-		if ( selectedObject.geometry === undefined ) {
+		var selected;
+		// TODO: handle multiple selection
+		for ( var i in editor.selected ) {
+			if ( editor.objects[ editor.selected[ i ].uuid ] ) selected = editor.selected[ i ];
+		}
+		if ( !selected ) return;
+
+		if ( selected.geometry === undefined ) {
 
 			alert( "Selected object doesn't have any geometry" );
 			return;
@@ -133,12 +137,12 @@ Menubar.File = function ( signals ) {
 
 		if ( exporter instanceof THREE.GeometryExporter ) {
 
-			output = JSON.stringify( exporter.parse( selectedObject.geometry ), null, '\t' );
+			output = JSON.stringify( exporter.parse( selected.geometry ), null, '\t' );
 			output = output.replace( /[\n\t]+([\d\.e\-\[\]]+)/g, '$1' );
 
 		} else {
 
-			output = exporter.parse( selectedObject.geometry );
+			output = exporter.parse( selected.geometry );
 
 		}
 
@@ -152,9 +156,16 @@ Menubar.File = function ( signals ) {
 
 	var exportObject = function ( exporterClass ) {
 
+		var selected;
+		// TODO: handle multiple selection
+		for ( var i in editor.selected ) {
+			if ( editor.objects[ editor.selected[ i ].uuid ] ) selected = editor.selected[ i ];
+		}
+		if ( !selected ) return;
+
 		var exporter = new exporterClass();
 
-		var output = JSON.stringify( exporter.parse( selectedObject ), null, '\t' );
+		var output = JSON.stringify( exporter.parse( selected ), null, '\t' );
 		output = output.replace( /[\n\t]+([\d\.e\-\[\]]+)/g, '$1' );
 
 		var blob = new Blob( [ output ], { type: 'text/plain' } );
@@ -169,7 +180,7 @@ Menubar.File = function ( signals ) {
 
 		var exporter = new exporterClass();
 
-		var output = JSON.stringify( exporter.parse( scene ), null, '\t' );
+		var output = JSON.stringify( exporter.parse( editor.scene ), null, '\t' );
 		output = output.replace( /[\n\t]+([\d\.e\-\[\]]+)/g, '$1' );
 
 		var blob = new Blob( [ output ], { type: 'text/plain' } );
@@ -180,19 +191,6 @@ Menubar.File = function ( signals ) {
 
 	};
 
-	// signals
-
-	signals.objectSelected.add( function ( object ) {
-
-		selectedObject = object;
-
-	} );
-
-	signals.sceneChanged.add( function ( object ) {
-
-		scene = object;
-
-	} );
 
 	return container;
 

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

@@ -4,7 +4,7 @@ Menubar.Help = function ( signals ) {
 	container.setClass( 'menu' );
 	container.onMouseOver( function () { options.setDisplay( 'block' ) } );
 	container.onMouseOut( function () { options.setDisplay( 'none' ) } );
-	container.onClick( function () { options.setDisplay( 'block' ) } );
+	container.onClick( function () { options.setDisplay( 'none' ) } );
 
 	var title = new UI.Panel();
 	title.setTextContent( 'Help' ).setColor( '#666' );

+ 1 - 1
editor/js/Menubar.js

@@ -1,4 +1,4 @@
-var Menubar = function ( signals ) {
+var Menubar = function ( editor, signals ) {
 
 	var container = new UI.Panel();
 	container.setPosition( 'absolute' );

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

@@ -35,12 +35,12 @@ Sidebar.Animation = function ( signals ) {
 		};
 	}
 	
-	
 	signals.objectAdded.add( function ( object ) {
 
-		console.log(object)
 		if (object instanceof THREE.Mesh){
+
 			if (object.geometry && object.geometry.animation){
+
 				var name = object.geometry.animation.name;
 				options[name] = name
 				Animations.setOptions(options);
@@ -57,11 +57,11 @@ Sidebar.Animation = function ( signals ) {
 				}
 				
 			}
+			
 		}
 
 	} );
 	
-
 	return container;
 
 }

+ 10 - 18
editor/js/Sidebar.Geometry.CubeGeometry.js

@@ -1,11 +1,9 @@
-Sidebar.Geometry.CubeGeometry = function ( signals, object ) {
+Sidebar.Geometry.CubeGeometry = function ( signals, geometry ) {
 
 	var container = new UI.Panel();
 	container.setBorderTop( '1px solid #ccc' );
 	container.setPaddingTop( '10px' );
 
-	var geometry = object.geometry;
-
 	// width
 
 	var widthRow = new UI.Panel();
@@ -70,23 +68,17 @@ Sidebar.Geometry.CubeGeometry = function ( signals, object ) {
 
 	function update() {
 
-		delete object.__webglInit; // TODO: Remove hack (WebGLRenderer refactoring)
-
-		object.geometry.dispose();
-
-		object.geometry = new THREE.CubeGeometry(
-			width.getValue(),
-			height.getValue(),
-			depth.getValue(),
-			widthSegments.getValue(),
-			heightSegments.getValue(),
-			depthSegments.getValue()
+		editor.remakeGeometry( geometry,
+			{
+				width: width.getValue(),
+				height: height.getValue(),
+				depth: depth.getValue(),
+				widthSegments: widthSegments.getValue(),
+				heightSegments: heightSegments.getValue(),
+				depthSegments: depthSegments.getValue()
+			}
 		);
 
-		object.geometry.computeBoundingSphere();
-
-		signals.objectChanged.dispatch( object );
-
 	}
 
 	return container;

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

@@ -1,11 +1,9 @@
-Sidebar.Geometry.CylinderGeometry = function ( signals, object ) {
+Sidebar.Geometry.CylinderGeometry = function ( signals, geometry ) {
 
 	var container = new UI.Panel();
 	container.setBorderTop( '1px solid #ccc' );
 	container.setPaddingTop( '10px' );
 
-	var geometry = object.geometry;
-
 	// radiusTop
 
 	var radiusTopRow = new UI.Panel();
@@ -36,15 +34,15 @@ Sidebar.Geometry.CylinderGeometry = function ( signals, object ) {
 
 	container.add( heightRow );
 
-	// radiusSegments
+	// radialSegments
 
-	var radiusSegmentsRow = new UI.Panel();
-	var radiusSegments = new UI.Integer( geometry.radiusSegments ).setRange( 1, Infinity ).onChange( update );
+	var radialSegmentsRow = new UI.Panel();
+	var radialSegments = new UI.Integer( geometry.radialSegments ).setRange( 1, Infinity ).onChange( update );
 
-	radiusSegmentsRow.add( new UI.Text( 'Radius segments' ).setWidth( '90px' ).setColor( '#666' ) );
-	radiusSegmentsRow.add( radiusSegments );
+	radialSegmentsRow.add( new UI.Text( 'Radius segments' ).setWidth( '90px' ).setColor( '#666' ) );
+	radialSegmentsRow.add( radialSegments );
 
-	container.add( radiusSegmentsRow );
+	container.add( radialSegmentsRow );
 
 	// heightSegments
 
@@ -70,23 +68,17 @@ Sidebar.Geometry.CylinderGeometry = function ( signals, object ) {
 
 	function update() {
 
-		delete object.__webglInit; // TODO: Remove hack (WebGLRenderer refactoring)
-
-		object.geometry.dispose();
-
-		object.geometry = new THREE.CylinderGeometry(
-			radiusTop.getValue(),
-			radiusBottom.getValue(),
-			height.getValue(),
-			radiusSegments.getValue(),
-			heightSegments.getValue(),
-			openEnded.getValue()
+		editor.remakeGeometry( geometry,
+			{
+				radiusTop: radiusTop.getValue(),
+				radiusBottom: radiusBottom.getValue(),
+				height: height.getValue(),
+				radialSegments: radialSegments.getValue(),
+				heightSegments: heightSegments.getValue(),
+				openEnded: openEnded.getValue()
+			}
 		);
 
-		object.geometry.computeBoundingSphere();
-
-		signals.objectChanged.dispatch( object );
-
 	}
 
 	return container;

+ 6 - 14
editor/js/Sidebar.Geometry.IcosahedronGeometry.js

@@ -1,11 +1,9 @@
-Sidebar.Geometry.IcosahedronGeometry = function ( signals, object ) {
+Sidebar.Geometry.IcosahedronGeometry = function ( signals, geometry ) {
 
 	var container = new UI.Panel();
 	container.setBorderTop( '1px solid #ccc' );
 	container.setPaddingTop( '10px' );
 
-	var geometry = object.geometry;
-
 	// radius
 
 	var radiusRow = new UI.Panel();
@@ -31,19 +29,13 @@ Sidebar.Geometry.IcosahedronGeometry = function ( signals, object ) {
 
 	function update() {
 
-		delete object.__webglInit; // TODO: Remove hack (WebGLRenderer refactoring)
-
-		object.geometry.dispose();
-
-		object.geometry = new THREE.IcosahedronGeometry(
-			radius.getValue(),
-			detail.getValue()
+		editor.remakeGeometry( geometry,
+			{
+				radius: radius.getValue(),
+				detail: detail.getValue()
+			}
 		);
 
-		object.geometry.computeBoundingSphere();
-
-		signals.objectChanged.dispatch( object );
-
 	}
 
 	return container;

+ 8 - 16
editor/js/Sidebar.Geometry.PlaneGeometry.js

@@ -1,11 +1,9 @@
-Sidebar.Geometry.PlaneGeometry = function ( signals, object ) {
+Sidebar.Geometry.PlaneGeometry = function ( signals, geometry ) {
 
 	var container = new UI.Panel();
 	container.setBorderTop( '1px solid #ccc' );
 	container.setPaddingTop( '10px' );
 
-	var geometry = object.geometry;
-
 	// width
 
 	var widthRow = new UI.Panel();
@@ -51,21 +49,15 @@ Sidebar.Geometry.PlaneGeometry = function ( signals, object ) {
 
 	function update() {
 
-		delete object.__webglInit; // TODO: Remove hack (WebGLRenderer refactoring)
-
-		object.geometry.dispose();
-
-		object.geometry = new THREE.PlaneGeometry(
-			width.getValue(),
-			height.getValue(),
-			widthSegments.getValue(),
-			heightSegments.getValue()
+		editor.remakeGeometry( geometry,
+			{
+				width: width.getValue(),
+				height: height.getValue(),
+				widthSegments: widthSegments.getValue(),
+				heightSegments: heightSegments.getValue()
+			}
 		);
 
-		object.geometry.computeBoundingSphere();
-
-		signals.objectChanged.dispatch( object );
-
 	}
 
 	return container;

+ 11 - 19
editor/js/Sidebar.Geometry.SphereGeometry.js

@@ -1,11 +1,9 @@
-Sidebar.Geometry.SphereGeometry = function ( signals, object ) {
+Sidebar.Geometry.SphereGeometry = function ( signals, geometry ) {
 
 	var container = new UI.Panel();
 	container.setBorderTop( '1px solid #ccc' );
 	container.setPaddingTop( '10px' );
 
-	var geometry = object.geometry;
-
 	// radius
 
 	var radiusRow = new UI.Panel();
@@ -81,24 +79,18 @@ Sidebar.Geometry.SphereGeometry = function ( signals, object ) {
 
 	function update() {
 
-		delete object.__webglInit; // TODO: Remove hack (WebGLRenderer refactoring)
-
-		object.geometry.dispose();
-
-		object.geometry = new THREE.SphereGeometry(
-			radius.getValue(),
-			widthSegments.getValue(),
-			heightSegments.getValue(),
-			phiStart.getValue(),
-			phiLength.getValue(),
-			thetaStart.getValue(),
-			thetaLength.getValue()
+		editor.remakeGeometry( geometry,
+			{
+				radius: radius.getValue(),
+				widthSegments: widthSegments.getValue(),
+				heightSegments: heightSegments.getValue(),
+				phiStart: phiStart.getValue(),
+				phiLength: phiLength.getValue(),
+				thetaStart: thetaStart.getValue(),
+				thetaLength: thetaLength.getValue()
+			}
 		);
 
-		object.geometry.computeBoundingSphere();
-
-		signals.objectChanged.dispatch( object );
-
 	}
 
 	return container;

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

@@ -1,11 +1,9 @@
-Sidebar.Geometry.TorusGeometry = function ( signals, object ) {
+Sidebar.Geometry.TorusGeometry = function ( signals, geometry ) {
 
 	var container = new UI.Panel();
 	container.setBorderTop( '1px solid #ccc' );
 	container.setPaddingTop( '10px' );
 
-	var geometry = object.geometry;
-
 	// radius
 
 	var radiusRow = new UI.Panel();
@@ -56,27 +54,20 @@ Sidebar.Geometry.TorusGeometry = function ( signals, object ) {
 
 	container.add( arcRow );
 
-
 	//
 
 	function update() {
 
-		delete object.__webglInit; // TODO: Remove hack (WebGLRenderer refactoring)
-
-		object.geometry.dispose();
-
-		object.geometry = new THREE.TorusGeometry(
-			radius.getValue(),
-			tube.getValue(),
-			radialSegments.getValue(),
-			tubularSegments.getValue(),
-			arc.getValue()
+		editor.remakeGeometry( geometry,
+			{
+				radius: radius.getValue(),
+				tube: tube.getValue(),
+				radialSegments: radialSegments.getValue(),
+				tubularSegments: tubularSegments.getValue(),
+				arc: arc.getValue()
+			}
 		);
 
-		object.geometry.computeBoundingSphere();
-
-		signals.objectChanged.dispatch( object );
-
 	}
 
 	return container;

+ 11 - 19
editor/js/Sidebar.Geometry.TorusKnotGeometry.js

@@ -1,11 +1,9 @@
-Sidebar.Geometry.TorusKnotGeometry = function ( signals, object ) {
+Sidebar.Geometry.TorusKnotGeometry = function ( signals, geometry ) {
 
 	var container = new UI.Panel();
 	container.setBorderTop( '1px solid #ccc' );
 	container.setPaddingTop( '10px' );
 
-	var geometry = object.geometry;
-
 	// radius
 
 	var radiusRow = new UI.Panel();
@@ -81,24 +79,18 @@ Sidebar.Geometry.TorusKnotGeometry = function ( signals, object ) {
 
 	function update() {
 
-		delete object.__webglInit; // TODO: Remove hack (WebGLRenderer refactoring)
-
-		object.geometry.dispose();
-
-		object.geometry = new THREE.TorusKnotGeometry(
-			radius.getValue(),
-			tube.getValue(),
-			radialSegments.getValue(),
-			tubularSegments.getValue(),
-			p.getValue(),
-			q.getValue(),
-			heightScale.getValue()
+		editor.remakeGeometry( geometry,
+			{
+				radius: radius.getValue(),
+				tube: tube.getValue(),
+				radialSegments: radialSegments.getValue(),
+				tubularSegments: tubularSegments.getValue(),
+				p: p.getValue(),
+				q: q.getValue(),
+				heightScale: heightScale.getValue()
+			}
 		);
 
-		object.geometry.computeBoundingSphere();
-
-		signals.objectChanged.dispatch( object );
-
 	}
 
 	return container;

+ 26 - 24
editor/js/Sidebar.Geometry.js

@@ -67,32 +67,32 @@ Sidebar.Geometry = function ( signals ) {
 
 	var parameters;
 
-
 	//
 
-	var selected = null;
+	var geometry = null;
 
 	function update() {
 
-		if ( selected ) {
+		if ( geometry ) {
 
-			selected.name = geometryName.getValue();
+			geometry.name = geometryName.getValue();
 
 		}
 
 	}
 
-	signals.objectSelected.add( function ( object ) {
+	signals.selected.add( function ( selected ) {
 
-		if ( object && object.geometry ) {
+		var selected = editor.listSelected( 'geometry' );
+		geometry = ( selected.length ) ? selected[0] : null;
 
-			selected = object.geometry;
+		if ( geometry ) {
 
 			container.setDisplay( 'block' );
 
-			objectType.setValue( getGeometryInstanceName( object.geometry ) );
+			objectType.setValue( getGeometryInstanceName( geometry ) );
 
-			updateFields( selected );
+			updateFields( geometry );
 
 			//
 
@@ -103,46 +103,48 @@ Sidebar.Geometry = function ( signals ) {
 
 			}
 
-			if ( selected instanceof THREE.PlaneGeometry ) {
+			if ( geometry instanceof THREE.PlaneGeometry ) {
 
-				parameters = new Sidebar.Geometry.PlaneGeometry( signals, object );
+				parameters = new Sidebar.Geometry.PlaneGeometry( signals, geometry );
 				container.add( parameters );
 
-			} else if ( selected instanceof THREE.CubeGeometry ) {
+			} else if ( geometry instanceof THREE.CubeGeometry ) {
 
-				parameters = new Sidebar.Geometry.CubeGeometry( signals, object );
+				parameters = new Sidebar.Geometry.CubeGeometry( signals, geometry );
 				container.add( parameters );
 
-			} else if ( selected instanceof THREE.CylinderGeometry ) {
+			} else if ( geometry instanceof THREE.CylinderGeometry ) {
 
-				parameters = new Sidebar.Geometry.CylinderGeometry( signals, object );
+				parameters = new Sidebar.Geometry.CylinderGeometry( signals, geometry );
 				container.add( parameters );
 
-			} else if ( selected instanceof THREE.SphereGeometry ) {
+			} else if ( geometry instanceof THREE.SphereGeometry ) {
 
-				parameters = new Sidebar.Geometry.SphereGeometry( signals, object );
+				parameters = new Sidebar.Geometry.SphereGeometry( signals, geometry );
 				container.add( parameters );
 
-			} else if ( selected instanceof THREE.IcosahedronGeometry ) {
+			} else if ( geometry instanceof THREE.IcosahedronGeometry ) {
 
-				parameters = new Sidebar.Geometry.IcosahedronGeometry( signals, object );
+				parameters = new Sidebar.Geometry.IcosahedronGeometry( signals, geometry );
 				container.add( parameters );
 
-			} else if ( selected instanceof THREE.TorusGeometry ) {
+			} else if ( geometry instanceof THREE.TorusGeometry ) {
 
-				parameters = new Sidebar.Geometry.TorusGeometry( signals, object );
+				parameters = new Sidebar.Geometry.TorusGeometry( signals, geometry );
 				container.add( parameters );
 
-			} else if ( selected instanceof THREE.TorusKnotGeometry ) {
+			} else if ( geometry instanceof THREE.TorusKnotGeometry ) {
 
-				parameters = new Sidebar.Geometry.TorusKnotGeometry( signals, object );
+				parameters = new Sidebar.Geometry.TorusKnotGeometry( signals, geometry );
 				container.add( parameters );
 
 			}
 
+			update();
+
 		} else {
 
-			selected = null;
+			geometry = null;
 
 			container.setDisplay( 'none' );
 

+ 85 - 30
editor/js/Sidebar.Material.js

@@ -215,17 +215,18 @@ Sidebar.Material = function ( signals ) {
 
 	container.add( materialWireframeRow );
 
-
 	//
 
-	var selected = null;
+	var material = null;
 	var selectedHasUvs = false;
 
 	function update() {
 
-		var material = selected.material;
 		var textureWarning = false;
 
+		// var selected = editor.list( 'selected' )[0];
+		// material = ( editor.materials[ selected.uuid ] ) ? selected : null;
+
 		if ( material ) {
 
 			material.name = materialName.getValue();
@@ -233,7 +234,6 @@ Sidebar.Material = function ( signals ) {
 			if ( material instanceof materialClasses[ materialClass.getValue() ] == false ) {
 
 				material = new materialClasses[ materialClass.getValue() ]();
-				selected.material = material;
 
 			}
 
@@ -275,6 +275,7 @@ Sidebar.Material = function ( signals ) {
 
 					material.map = mapEnabled ? materialMap.getValue() : null;
 					material.needsUpdate = true;
+					// TODO: update all buffers with this material
 					selected.geometry.buffersNeedUpdate = true;
 					selected.geometry.uvsNeedUpdate = true;
 
@@ -445,7 +446,7 @@ Sidebar.Material = function ( signals ) {
 
 		for ( var property in properties ) {
 
-			properties[ property ].setDisplay( selected.material[ property ] !== undefined ? '' : 'none' );
+			properties[ property ].setDisplay( material[ property ] !== undefined ? '' : 'none' );
 
 		}
 
@@ -463,16 +464,19 @@ Sidebar.Material = function ( signals ) {
 
 	// events
 
-	signals.objectSelected.add( function ( object ) {
+	signals.selected.add( function ( selected ) {
+
+		var selected = editor.listSelected( 'material' );
+		material = ( selected.length ) ? selected[0] : null;
 
-		if ( object && object.material ) {
+		if ( material ) {
 
-			selected = object;
-			selectedHasUvs = object.geometry.faceVertexUvs[ 0 ].length > 0;
+			// selected = object;
+			// selectedHasUvs = object.geometry.faceVertexUvs[ 0 ].length > 0;
 
 			container.setDisplay( '' );
 
-			var material = object.material;
+			// var material = object.material;
 
 			materialName.setValue( material.name );
 			materialClass.setValue( getMaterialInstanceName( material ) );
@@ -509,55 +513,104 @@ Sidebar.Material = function ( signals ) {
 
 			if ( material.map !== undefined ) {
 
-				if ( selectedHasUvs ) {
+				// if ( selectedHasUvs ) {
 
-					materialMapEnabled.setValue( material.map !== null );
-					materialMap.setValue( material.map );
+					if ( material.map !== null ) {
 
-				} else {
+						materialMapEnabled.setValue( true );
+						materialMap.setValue( material.map );
 
-					console.warn( "Can't set texture, model doesn't have texture coordinates" );
+					} else {
 
-				}
+						materialMapEnabled.setValue( false );
+
+					}
+
+				// } else {
+
+				// 	console.warn( "Can't set texture, model doesn't have texture coordinates" );
+
+				// }
 
 			}
 
 			/*
 			if ( material.lightMap !== undefined ) {
 
-				materialLightMapEnabled.setValue( material.lightMap !== null );
-				materialLightMap.setValue( material.lightMap );
+				if ( material.lightMap !== null ) {
+
+					materialLightMapEnabled.setValue( true );
+					materialLightMap.setValue( material.lightMap );
+
+				} else {
+
+					materialLightMapEnabled.setValue( false );
+
+				}
 
 			}
 			*/
 
 			if ( material.bumpMap !== undefined ) {
 
-				materialBumpMapEnabled.setValue( material.bumpMap !== null );
-				materialBumpMap.setValue( material.bumpMap );
-				materialBumpScale.setValue( material.bumpScale );
+				if ( material.bumpMap !== null ) {
+
+					materialBumpMapEnabled.setValue( true );
+					materialBumpMap.setValue( material.bumpMap );
+					materialBumpScale.setValue( material.bumpScale );
+
+				} else {
+
+					materialBumpMapEnabled.setValue( false );
+					materialBumpScale.setValue( 1 );
+
+				}
 
 			}
 
 			if ( material.normalMap !== undefined ) {
 
-				materialNormalMapEnabled.setValue( material.normalMap !== null );
-				materialNormalMap.setValue( material.normalMap );
+				if ( material.normalMap !== null ) {
+
+					materialNormalMapEnabled.setValue( true );
+					materialNormalMap.setValue( material.normalMap );
+
+				} else {
+
+					materialNormalMapEnabled.setValue( false );
+
+				}
 
 			}
 
 			if ( material.specularMap !== undefined ) {
 
-				materialSpecularMapEnabled.setValue( material.specularMap !== null );
-				materialSpecularMap.setValue( material.specularMap );
+				if ( material.specularMap !== null ) {
+
+					materialSpecularMapEnabled.setValue( true );
+					materialSpecularMap.setValue( material.specularMap );
+
+				} else {
+
+					materialSpecularMapEnabled.setValue( false );
+
+				}
 
 			}
 
 			if ( material.envMap !== undefined ) {
 
-				materialEnvMapEnabled.setValue( material.envMap !== null );
-				materialEnvMap.setValue( material.envMap );
-				materialReflectivity.setValue( material.reflectivity );
+				if ( material.envMap !== null ) {
+
+					materialEnvMapEnabled.setValue( true );
+					materialEnvMap.setValue( material.envMap );
+					materialReflectivity.setValue( material.reflectivity );
+
+				} else {
+
+					materialEnvMapEnabled.setValue( false );
+
+				}
 
 			}
 
@@ -587,10 +640,12 @@ Sidebar.Material = function ( signals ) {
 
 			updateRows();
 
+			update();
+
 		} else {
 
-			selected = null;
-			selectedHasUvs = false;
+			// selected = null;
+			// selectedHasUvs = false;
 
 			container.setDisplay( 'none' );
 

+ 158 - 105
editor/js/Sidebar.Object3D.js

@@ -9,25 +9,43 @@ Sidebar.Object3D = function ( signals ) {
 	container.add( objectType );
 	container.add( new UI.Break(), new UI.Break() );
 
+	// name
+
+	var objectNameRow = new UI.Panel();
+	var objectName = new UI.Input().setWidth( '150px' ).setColor( '#444' ).setFontSize( '12px' ).onChange( update );
+	objectNameRow.add( new UI.Text( 'Name' ).setWidth( '90px' ).setColor( '#666' ) );
+	objectNameRow.add( objectName );
+	container.add( objectNameRow );
+
 	// parent
 
 	var objectParentRow = new UI.Panel();
 	var objectParent = new UI.Select().setWidth( '150px' ).setColor( '#444' ).setFontSize( '12px' ).onChange( update );
-
-	objectParentRow.add( new UI.Text( 'Parent' ).setWidth( '90px' ).setColor( '#666' ) );
+	var parentLabel = new UI.Text( 'Parent' ).setWidth( '90px' ).setColor( '#0080f0' );
+	parentLabel.onClick( function(){  editor.select( editor.objects[ objectParent.getValue() ] ) } );
+	objectParentRow.add( parentLabel );
 	objectParentRow.add( objectParent );
-
 	container.add( objectParentRow );
 
-	// name
+	// geometry
 
-	var objectNameRow = new UI.Panel();
-	var objectName = new UI.Input().setWidth( '150px' ).setColor( '#444' ).setFontSize( '12px' ).onChange( update );
+	var objectGeometryRow = new UI.Panel();
+	var objectGeometry = new UI.Select().setWidth( '150px' ).setColor( '#444' ).setFontSize( '12px' ).onChange( update );
+	var geometryLabel = new UI.Text( 'Geometry' ).setWidth( '90px' ).setColor( '#0080f0' );
+	geometryLabel.onClick( function(){  editor.select( editor.geometries[ objectGeometry.getValue() ] ) } );
+	objectGeometryRow.add( geometryLabel );
+	objectGeometryRow.add( objectGeometry );
+	container.add( objectGeometryRow );
 
-	objectNameRow.add( new UI.Text( 'Name' ).setWidth( '90px' ).setColor( '#666' ) );
-	objectNameRow.add( objectName );
+	// material
 
-	container.add( objectNameRow );
+	var objectMaterialRow = new UI.Panel();
+	var objectMaterial = new UI.Select().setWidth( '150px' ).setColor( '#444' ).setFontSize( '12px' ).onChange( update );
+	var materialLabel = new UI.Text( 'Material' ).setWidth( '90px' ).setColor( '#0080f0' );
+	materialLabel.onClick( function(){  editor.select( editor.materials[ objectMaterial.getValue() ] ) } );
+	objectMaterialRow.add( materialLabel );
+	objectMaterialRow.add( objectMaterial );
+	container.add( objectMaterialRow );
 
 	// position
 
@@ -35,10 +53,8 @@ Sidebar.Object3D = function ( signals ) {
 	var objectPositionX = new UI.Number().setWidth( '50px' ).onChange( update );
 	var objectPositionY = new UI.Number().setWidth( '50px' ).onChange( update );
 	var objectPositionZ = new UI.Number().setWidth( '50px' ).onChange( update );
-
 	objectPositionRow.add( new UI.Text( 'Position' ).setWidth( '90px' ).setColor( '#666' ) );
 	objectPositionRow.add( objectPositionX, objectPositionY, objectPositionZ );
-
 	container.add( objectPositionRow );
 
 	// rotation
@@ -47,10 +63,8 @@ Sidebar.Object3D = function ( signals ) {
 	var objectRotationX = new UI.Number().setWidth( '50px' ).onChange( update );
 	var objectRotationY = new UI.Number().setWidth( '50px' ).onChange( update );
 	var objectRotationZ = new UI.Number().setWidth( '50px' ).onChange( update );
-
 	objectRotationRow.add( new UI.Text( 'Rotation' ).setWidth( '90px' ).setColor( '#666' ) );
 	objectRotationRow.add( objectRotationX, objectRotationY, objectRotationZ );
-
 	container.add( objectRotationRow );
 
 	// scale
@@ -60,7 +74,6 @@ Sidebar.Object3D = function ( signals ) {
 	var objectScaleX = new UI.Number( 1 ).setWidth( '50px' ).onChange( updateScaleX );
 	var objectScaleY = new UI.Number( 1 ).setWidth( '50px' ).onChange( updateScaleY );
 	var objectScaleZ = new UI.Number( 1 ).setWidth( '50px' ).onChange( updateScaleZ );
-
 	objectScaleRow.add( new UI.Text( 'Scale' ).setWidth( '90px' ).setColor( '#666' ) );
 	objectScaleRow.add( objectScaleLock );
 	objectScaleRow.add( objectScaleX, objectScaleY, objectScaleZ );
@@ -71,100 +84,80 @@ Sidebar.Object3D = function ( signals ) {
 
 	var objectFovRow = new UI.Panel();
 	var objectFov = new UI.Number().onChange( update );
-
 	objectFovRow.add( new UI.Text( 'Fov' ).setWidth( '90px' ).setColor( '#666' ) );
 	objectFovRow.add( objectFov );
-
 	container.add( objectFovRow );
 
 	// near
 
 	var objectNearRow = new UI.Panel();
 	var objectNear = new UI.Number().onChange( update );
-
 	objectNearRow.add( new UI.Text( 'Near' ).setWidth( '90px' ).setColor( '#666' ) );
 	objectNearRow.add( objectNear );
-
 	container.add( objectNearRow );
 
 	// far
 
 	var objectFarRow = new UI.Panel();
 	var objectFar = new UI.Number().onChange( update );
-
 	objectFarRow.add( new UI.Text( 'Far' ).setWidth( '90px' ).setColor( '#666' ) );
 	objectFarRow.add( objectFar );
-
 	container.add( objectFarRow );
 
 	// intensity
 
 	var objectIntensityRow = new UI.Panel();
 	var objectIntensity = new UI.Number().setRange( 0, Infinity ).onChange( update );
-
 	objectIntensityRow.add( new UI.Text( 'Intensity' ).setWidth( '90px' ).setColor( '#666' ) );
 	objectIntensityRow.add( objectIntensity );
-
 	container.add( objectIntensityRow );
 
 	// color
 
 	var objectColorRow = new UI.Panel();
 	var objectColor = new UI.Color().onChange( update );
-
 	objectColorRow.add( new UI.Text( 'Color' ).setWidth( '90px' ).setColor( '#666' ) );
 	objectColorRow.add( objectColor );
-
 	container.add( objectColorRow );
 
 	// ground color
 
 	var objectGroundColorRow = new UI.Panel();
 	var objectGroundColor = new UI.Color().onChange( update );
-
 	objectGroundColorRow.add( new UI.Text( 'Ground color' ).setWidth( '90px' ).setColor( '#666' ) );
 	objectGroundColorRow.add( objectGroundColor );
-
 	container.add( objectGroundColorRow );
 
 	// distance
 
 	var objectDistanceRow = new UI.Panel();
 	var objectDistance = new UI.Number().setRange( 0, Infinity ).onChange( update );
-
 	objectDistanceRow.add( new UI.Text( 'Distance' ).setWidth( '90px' ).setColor( '#666' ) );
 	objectDistanceRow.add( objectDistance );
-
 	container.add( objectDistanceRow );
 
 	// angle
 
 	var objectAngleRow = new UI.Panel();
 	var objectAngle = new UI.Number().setPrecision( 3 ).setRange( 0, Math.PI / 2 ).onChange( update );
-
 	objectAngleRow.add( new UI.Text( 'Angle' ).setWidth( '90px' ).setColor( '#666' ) );
 	objectAngleRow.add( objectAngle );
-
 	container.add( objectAngleRow );
 
 	// exponent
 
 	var objectExponentRow = new UI.Panel();
 	var objectExponent = new UI.Number().setRange( 0, Infinity ).onChange( update );
-
 	objectExponentRow.add( new UI.Text( 'Exponent' ).setWidth( '90px' ).setColor( '#666' ) );
 	objectExponentRow.add( objectExponent );
-
 	container.add( objectExponentRow );
 
 	// visible
 
 	var objectVisibleRow = new UI.Panel();
 	var objectVisible = new UI.Checkbox().onChange( update );
-
 	objectVisibleRow.add( new UI.Text( 'Visible' ).setWidth( '90px' ).setColor( '#666' ) );
 	objectVisibleRow.add( objectVisible );
-
 	container.add( objectVisibleRow );
 
 	// user data
@@ -193,18 +186,19 @@ Sidebar.Object3D = function ( signals ) {
 
 	container.add( objectUserDataRow );
 
-
 	//
 
-	var selected = null;
+	var object = null;
 
-	var scene = null;
+	var scene = editor.scene;
+
+	//
 
 	function updateScaleX() {
 
 		if ( objectScaleLock.getValue() === true ) {
 
-			var scale = objectScaleX.getValue() / selected.scale.x;
+			var scale = objectScaleX.getValue() / object.scale.x;
 
 			objectScaleY.setValue( objectScaleY.getValue() * scale );
 			objectScaleZ.setValue( objectScaleZ.getValue() * scale );
@@ -219,7 +213,7 @@ Sidebar.Object3D = function ( signals ) {
 
 		if ( objectScaleLock.getValue() === true ) {
 
-			var scale = objectScaleY.getValue() / selected.scale.y;
+			var scale = objectScaleY.getValue() / object.scale.y;
 
 			objectScaleX.setValue( objectScaleX.getValue() * scale );
 			objectScaleZ.setValue( objectScaleZ.getValue() * scale );
@@ -234,7 +228,7 @@ Sidebar.Object3D = function ( signals ) {
 
 		if ( objectScaleLock.getValue() === true ) {
 
-			var scale = objectScaleZ.getValue() / selected.scale.z;
+			var scale = objectScaleZ.getValue() / object.scale.z;
 
 			objectScaleX.setValue( objectScaleX.getValue() * scale );
 			objectScaleY.setValue( objectScaleY.getValue() * scale );
@@ -247,17 +241,17 @@ Sidebar.Object3D = function ( signals ) {
 
 	function update() {
 
-		if ( selected ) {
+		if ( object ) {
 
-			selected.name = objectName.getValue();
+			object.name = objectName.getValue();
 
-			if ( selected.parent !== undefined ) {
+			if ( object.parent !== undefined ) {
 
-				var newParentId = parseInt( objectParent.getValue() );
+				var newParentUuid = objectParent.getValue();
 
-				if ( selected.parent.id !== newParentId && selected.id !== newParentId ) {
+				if ( object.parent.uuid !== newParentUuid && object.uuid !== newParentUuid ) {
 
-					var parent = scene.getObjectById( newParentId, true );
+					var parent = editor.objects[newParentUuid];
 
 					if ( parent === undefined ) {
 
@@ -265,7 +259,7 @@ Sidebar.Object3D = function ( signals ) {
 
 					}
 
-					parent.add( selected );
+					parent.add( object );
 
 					signals.sceneChanged.dispatch( scene );
 
@@ -273,78 +267,109 @@ Sidebar.Object3D = function ( signals ) {
 
 			}
 
-			selected.position.x = objectPositionX.getValue();
-			selected.position.y = objectPositionY.getValue();
-			selected.position.z = objectPositionZ.getValue();
 
-			selected.rotation.x = objectRotationX.getValue();
-			selected.rotation.y = objectRotationY.getValue();
-			selected.rotation.z = objectRotationZ.getValue();
+			if ( object.geometry !== undefined ) {
+
+				var newGeometryUUid = objectGeometry.getValue();
+
+				if ( object.geometry.uuid !== newGeometryUUid && object.uuid !== newGeometryUUid ) {
+
+					object.geometry = editor.geometries[newGeometryUUid];
+
+					// TODO: Update Geometry;
+
+					signals.objectChanged.dispatch( object );
+
+				}
+
+			}
+
+			if ( object.material !== undefined ) {
+
+				var newMaterialUUid = objectMaterial.getValue();
+
+				if ( object.material.uuid !== newMaterialUUid && object.uuid !== newMaterialUUid ) {
+
+					object.material = editor.materials[newMaterialUUid];
+
+					signals.objectChanged.dispatch( object );
+
+				}
+
+			}
+
+			object.position.x = objectPositionX.getValue();
+			object.position.y = objectPositionY.getValue();
+			object.position.z = objectPositionZ.getValue();
+
+			object.rotation.x = objectRotationX.getValue();
+			object.rotation.y = objectRotationY.getValue();
+			object.rotation.z = objectRotationZ.getValue();
 
-			selected.scale.x = objectScaleX.getValue();
-			selected.scale.y = objectScaleY.getValue();
-			selected.scale.z = objectScaleZ.getValue();
+			object.scale.x = objectScaleX.getValue();
+			object.scale.y = objectScaleY.getValue();
+			object.scale.z = objectScaleZ.getValue();
 
-			if ( selected.fov !== undefined ) {
+			if ( object.fov !== undefined ) {
 
-				selected.fov = objectFov.getValue();
-				selected.updateProjectionMatrix();
+				object.fov = objectFov.getValue();
+				object.updateProjectionMatrix();
 
 			}
 
-			if ( selected.near !== undefined ) {
+			if ( object.near !== undefined ) {
 
-				selected.near = objectNear.getValue();
+				object.near = objectNear.getValue();
 
 			}
 
-			if ( selected.far !== undefined ) {
+			if ( object.far !== undefined ) {
 
-				selected.far = objectFar.getValue();
+				object.far = objectFar.getValue();
 
 			}
 
-			if ( selected.intensity !== undefined ) {
+			if ( object.intensity !== undefined ) {
 
-				selected.intensity = objectIntensity.getValue();
+				object.intensity = objectIntensity.getValue();
 
 			}
 
-			if ( selected.color !== undefined ) {
+			if ( object.color !== undefined ) {
 
-				selected.color.setHex( objectColor.getHexValue() );
+				object.color.setHex( objectColor.getHexValue() );
 
 			}
 
-			if ( selected.groundColor !== undefined ) {
+			if ( object.groundColor !== undefined ) {
 
-				selected.groundColor.setHex( objectGroundColor.getHexValue() );
+				object.groundColor.setHex( objectGroundColor.getHexValue() );
 
 			}
 
-			if ( selected.distance !== undefined ) {
+			if ( object.distance !== undefined ) {
 
-				selected.distance = objectDistance.getValue();
+				object.distance = objectDistance.getValue();
 
 			}
 
-			if ( selected.angle !== undefined ) {
+			if ( object.angle !== undefined ) {
 
-				selected.angle = objectAngle.getValue();
+				object.angle = objectAngle.getValue();
 
 			}
 
-			if ( selected.exponent !== undefined ) {
+			if ( object.exponent !== undefined ) {
 
-				selected.exponent = objectExponent.getValue();
+				object.exponent = objectExponent.getValue();
 
 			}
 
-			selected.visible = objectVisible.getValue();
+			object.visible = objectVisible.getValue();
 
 			try {
 
-				selected.userData = JSON.parse( objectUserData.getValue() );
+				object.userData = JSON.parse( objectUserData.getValue() );
 
 			} catch ( error ) {
 
@@ -352,7 +377,7 @@ Sidebar.Object3D = function ( signals ) {
 
 			}
 
-			signals.objectChanged.dispatch( selected );
+			signals.objectChanged.dispatch( object );
 
 		}
 
@@ -362,6 +387,8 @@ Sidebar.Object3D = function ( signals ) {
 
 		var properties = {
 			'parent': objectParentRow,
+			'geometry': objectGeometryRow,
+			'material': objectMaterialRow,
 			'fov': objectFovRow,
 			'near': objectNearRow,
 			'far': objectFarRow,
@@ -375,7 +402,7 @@ Sidebar.Object3D = function ( signals ) {
 
 		for ( var property in properties ) {
 
-			properties[ property ].setDisplay( selected[ property ] !== undefined ? '' : 'none' );
+			properties[ property ].setDisplay( object[ property ] !== undefined ? '' : 'none' );
 
 		}
 
@@ -383,7 +410,7 @@ Sidebar.Object3D = function ( signals ) {
 
 	function updateTransformRows() {
 
-		if ( selected instanceof THREE.Light || ( selected instanceof THREE.Object3D && selected.userData.targetInverse ) ) {
+		if ( object instanceof THREE.Light || ( object instanceof THREE.Object3D && object.userData.targetInverse ) ) {
 
 			objectRotationRow.setDisplay( 'none' );
 			objectScaleRow.setDisplay( 'none' );
@@ -423,55 +450,81 @@ Sidebar.Object3D = function ( signals ) {
 
 	// events
 
-	signals.sceneChanged.add( function ( object ) {
+	signals.sceneChanged.add( function () {
+
 
-		scene = object;
 
-		var options = {};
+	} );
 
-		options[ scene.id ] = 'Scene';
+	signals.selected.add( function ( selected ) {
+			
+		var selected = editor.listSelected( 'object' );
+		object = ( selected.length ) ? selected[0] : null;
 
-		( function addObjects( objects ) {
+		updateUI();
 
-			for ( var i = 0, l = objects.length; i < l; i ++ ) {
+	} );
 
-				var object = objects[ i ];
+	signals.objectChanged.add( function ( changedObject ) {
 
-				options[ object.id ] = object.name;
+		if ( object === changedObject ) updateUI();
 
-				addObjects( object.children );
+	} );
 
-			}
+	function updateUI() {
 
-		} )( object.children );
+		if ( !object ) {
 
-		objectParent.setOptions( options );
+			container.setDisplay( 'none' );
+			return;
 
-	} );
+		}
 
-	signals.objectSelected.add( function ( object ) {
+		container.setDisplay( 'block' );
 
-		selected = object;
-		updateUI();
+		objectType.setValue( getObjectInstanceName( object ) );
 
-	} );
-	signals.objectChanged.add( function ( object ) {
+		var allObjects = {};
+		var allGeometries = {};
+		var allMaterials = {};
 
-		if ( selected === object ) updateUI();
+		for ( var uuid in editor.objects ) {
 
-	} );
+			if ( object.uuid != uuid ) allObjects[ uuid ] = editor.objects[ uuid ].name;
 
-	function updateUI() {
+		}
 
-		container.setDisplay( 'block' );
+		for ( var uuid in editor.geometries ) {
 
-		var object = selected;
+			allGeometries[ uuid ] = editor.geometries[ uuid ].name;
 
-		objectType.setValue( getObjectInstanceName( object ) );
+		}
+
+		for ( var uuid in editor.materials ) {
+
+			allMaterials[ uuid ] = editor.materials[ uuid ].name;
+
+		}
+
+		objectParent.setOptions( allObjects );
+		objectGeometry.setOptions( allGeometries );
+		objectMaterial.setOptions( allMaterials );
 
 		if ( object.parent !== undefined ) {
 
-			objectParent.setValue( object.parent.id );
+			objectParent.setValue( object.parent.uuid );
+
+		}
+
+		if ( object.geometry !== undefined ) {
+
+			objectGeometry.setValue( object.geometry.uuid );
+
+		}
+
+		if ( object.material !== undefined ) {
+
+			objectMaterial.setValue( object.material.uuid );
 
 		}
 

+ 74 - 0
editor/js/Sidebar.Outliner.Geometries.js

@@ -0,0 +1,74 @@
+Sidebar.Outliner.Geometries = function ( signals ) {
+
+	var container = new UI.Panel();
+	container.name = "GEO";
+	container.setPadding( '10px' );
+
+	var outliner = new UI.FancySelect().setWidth( '100%' ).setHeight('170px').setColor( '#444' ).setFontSize( '12px' ).onChange( selectFromOutliner );
+	container.add( outliner );
+
+	var geometries = null;
+
+  function getGeometries() {
+
+		var options = {};
+
+		for ( var i in editor.geometries ) {
+
+			var geometry = editor.geometries[ i ];
+
+			if ( geometry.name == '') geometry.name = 'Geometry' + geometry.id;
+
+			options[ i ] = geometry.name;
+
+		}
+
+		outliner.setOptions( options );
+    getSelected();
+
+  }
+
+  function getSelected() {
+
+    var selectedUuids = [];
+
+    for ( var uuid in editor.selected ) {
+
+      if ( editor.geometries[uuid] ) selectedUuids.push(uuid);
+
+    }
+
+    // TODO: implement multiple selection
+    outliner.setValue( selectedUuids.length ? selectedUuids[0] : null );
+
+  }
+
+	function selectFromOutliner() {
+
+		var uuid = outliner.getValue();
+
+		editor.select( editor.geometries[uuid] );
+
+	}
+
+	// events
+
+  var timeout;
+
+	signals.sceneChanged.add( function () {
+
+    clearTimeout( timeout );
+
+    timeout = setTimeout( function () {
+
+      getGeometries();
+
+    }, 100 );
+
+	} );
+
+  signals.selected.add( getSelected );
+
+	return container;
+
+}

+ 74 - 0
editor/js/Sidebar.Outliner.Materials.js

@@ -0,0 +1,74 @@
+Sidebar.Outliner.Materials = function ( signals ) {
+
+	var container = new UI.Panel();
+	container.name = "MAT";
+	container.setPadding( '10px' );
+
+	var outliner = new UI.FancySelect().setWidth( '100%' ).setHeight('170px').setColor( '#444' ).setFontSize( '12px' ).onChange( selectFromOutliner );
+	container.add( outliner );
+
+	var materials = null;
+
+  function getMaterials() {
+
+		var options = {};
+
+		for ( var i in editor.materials ) {
+
+			var material = editor.materials[ i ];
+
+			if ( material.name == '') material.name = 'Material' + material.id;
+
+			options[ i ] = material.name;
+
+		}
+
+		outliner.setOptions( options );
+    getSelected();
+
+  }
+
+  function getSelected() {
+
+    var selectedUuids = [];
+
+    for ( var uuid in editor.selected ) {
+
+      if ( editor.materials[uuid] ) selectedUuids.push(uuid);
+
+    }
+
+    // TODO: implement multiple selection
+    outliner.setValue( selectedUuids.length ? selectedUuids[0] : null );
+
+  }
+
+	function selectFromOutliner() {
+
+		var uuid = outliner.getValue();
+
+		editor.select( editor.materials[uuid] );
+
+	}
+
+	// events
+
+  var timeout;
+
+	signals.sceneChanged.add( function () {
+
+    clearTimeout( timeout );
+
+    timeout = setTimeout( function () {
+
+      getMaterials();
+
+    }, 100 );
+
+	} );
+
+  signals.selected.add( getSelected );
+
+	return container;
+
+}

+ 68 - 86
editor/js/Sidebar.Scene.js → editor/js/Sidebar.Outliner.Scene.js

@@ -1,15 +1,10 @@
-Sidebar.Scene = function ( signals ) {
-
-	var selected = null;
+Sidebar.Outliner.Scene = function ( signals ) {
 
 	var container = new UI.Panel();
+	container.name = "SCENE";
 	container.setPadding( '10px' );
-	container.setBorderTop( '1px solid #ccc' );
-
-	container.add( new UI.Text( 'SCENE' ).setColor( '#666' ) );
-	container.add( new UI.Break(), new UI.Break() );
 
-	var outliner = new UI.FancySelect().setWidth( '100%' ).setHeight('140px').setColor( '#444' ).setFontSize( '12px' ).onChange( updateOutliner );
+	var outliner = new UI.FancySelect().setWidth( '100%' ).setHeight('128px').setColor( '#444' ).setFontSize( '12px' ).onChange( selectFromOutliner );
 	container.add( outliner );
 	container.add( new UI.Break() );
 
@@ -22,59 +17,47 @@ Sidebar.Scene = function ( signals ) {
 		'Fog': 'Linear',
 		'FogExp2': 'Exponential'
 
-	} ).setWidth( '150px' ).setColor( '#444' ).setFontSize( '12px' ).onChange( updateFogType );
-
+	} ).setWidth( '150px' ).setColor( '#444' ).setFontSize( '12px' ).onChange(
+		function() { editor.setFog( { fogType: fogType.getValue() } ) }
+	);
 	fogTypeRow.add( new UI.Text( 'Fog' ).setWidth( '90px' ).setColor( '#666' ) );
 	fogTypeRow.add( fogType );
-
 	container.add( fogTypeRow );
 
-	// fog color
-
 	var fogColorRow = new UI.Panel();
 	fogColorRow.setDisplay( 'none' );
-
-	var fogColor = new UI.Color().setValue( '#aaaaaa' ).onChange( updateFogColor );
-
+	var fogColor = new UI.Color().setValue( '#aaaaaa' ).onChange(
+		function() { editor.setFog( { color: fogColor.getHexValue() } ) }
+	);
 	fogColorRow.add( new UI.Text( 'Fog color' ).setWidth( '90px' ).setColor( '#666' ) );
 	fogColorRow.add( fogColor );
-
 	container.add( fogColorRow );
 
-	// fog near
-
 	var fogNearRow = new UI.Panel();
 	fogNearRow.setDisplay( 'none' );
-
-	var fogNear = new UI.Number( 1 ).setWidth( '60px' ).setRange( 0, Infinity ).onChange( updateFogParameters );
-
+	var fogNear = new UI.Number( 1 ).setWidth( '60px' ).setRange( 0, Infinity ).onChange(
+		function() { editor.setFog( { near: fogNear.getValue() } ) }
+	);
 	fogNearRow.add( new UI.Text( 'Fog near' ).setWidth( '90px' ).setColor( '#666' ) );
 	fogNearRow.add( fogNear );
-
 	container.add( fogNearRow );
 
 	var fogFarRow = new UI.Panel();
 	fogFarRow.setDisplay( 'none' );
-
-	// fog far
-
-	var fogFar = new UI.Number( 5000 ).setWidth( '60px' ).setRange( 0, Infinity ).onChange( updateFogParameters );
-
+	var fogFar = new UI.Number( 5000 ).setWidth( '60px' ).setRange( 0, Infinity ).onChange(
+		function() { editor.setFog( { far: fogFar.getValue() } ) }
+	);
 	fogFarRow.add( new UI.Text( 'Fog far' ).setWidth( '90px' ).setColor( '#666' ) );
 	fogFarRow.add( fogFar );
-
 	container.add( fogFarRow );
 
-	// fog density
-
 	var fogDensityRow = new UI.Panel();
 	fogDensityRow.setDisplay( 'none' );
-
-	var fogDensity = new UI.Number( 0.00025 ).setWidth( '60px' ).setRange( 0, 0.1 ).setPrecision( 5 ).onChange( updateFogParameters );
-
+	var fogDensity = new UI.Number( 0.00025 ).setWidth( '60px' ).setRange( 0, 0.1 ).setPrecision( 5 ).onChange(
+		function() { editor.setFog( { density: fogDensity.getValue() } ) }
+	);
 	fogDensityRow.add( new UI.Text( 'Fog density' ).setWidth( '90px' ).setColor( '#666' ) );
 	fogDensityRow.add( fogDensity );
-
 	container.add( fogDensityRow );
 
 	//
@@ -105,80 +88,59 @@ Sidebar.Scene = function ( signals ) {
 
 	}
 
-	function updateOutliner() {
-
-		var id = parseInt( outliner.getValue() );
-
-		scene.traverse( function ( node ) {
-
-			if ( node.id === id ) {
-
-				signals.objectSelected.dispatch( node );
-				return;
+	function selectFromOutliner() {
 
-			}
+		var uuid = outliner.getValue();
 
-		} );
+		editor.select( editor.objects[ uuid ] );
 
 	}
 
-	function updateFogType() {
-
-		var type = fogType.getValue();
-		signals.fogTypeChanged.dispatch( type );
+	function getScene() {
 
-		refreshFogUI();
+		var options = {};
 
-	}
+		var scene = editor.scene;
 
-	function refreshFogUI() {
+		options[ scene.uuid ] = scene.name + ' <span style="color: #aaa">- ' + getObjectType( scene ) + '</span>';
 
-		var type = fogType.getValue();
+		( function addObjects( objects, pad ) {
 
-		fogColorRow.setDisplay( type === 'None' ? 'none' : '' );
-		fogNearRow.setDisplay( type === 'Fog' ? '' : 'none' );
-		fogFarRow.setDisplay( type === 'Fog' ? '' : 'none' );
-		fogDensityRow.setDisplay( type === 'FogExp2' ? '' : 'none' );
+			for ( var i = 0, l = objects.length; i < l; i ++ ) {
 
-	}
+				var object = objects[ i ];
 
-	function updateFogColor() {
+				options[ object.uuid ] = pad + object.name + ' <span style="color: #aaa">- ' + getObjectType( object ) + '</span>';
 
-		signals.fogColorChanged.dispatch( fogColor.getHexValue() );
+				addObjects( object.children, pad + '&nbsp;&nbsp;&nbsp;' );
 
-	}
+			}
 
-	function updateFogParameters() {
+		} )( scene.children, '&nbsp;&nbsp;&nbsp;' );
 
-		signals.fogParametersChanged.dispatch( fogNear.getValue(), fogFar.getValue(), fogDensity.getValue() );
+		outliner.setOptions( options );
+		getSelected();
+		getFog();
 
 	}
 
-	// events
+  function getSelected() {
 
-	signals.sceneChanged.add( function ( object ) {
+    var selectedUuids = [];
 
-		scene = object;
+    for ( var uuid in editor.selected ) {
 
-		var options = {};
+      if ( editor.objects[uuid] ) selectedUuids.push(uuid);
 
-		options[ scene.id ] = scene.name + ' <span style="color: #aaa">- ' + getObjectType( scene ) + '</span>';
+    }
 
-		( function addObjects( objects, pad ) {
+    outliner.setValue( selectedUuids.length ? selectedUuids : null );
 
-			for ( var i = 0, l = objects.length; i < l; i ++ ) {
+  }
 
-				var object = objects[ i ];
-
-				options[ object.id ] = pad + object.name + ' <span style="color: #aaa">- ' + getObjectType( object ) + '</span>';
+	function getFog() {
 
-				addObjects( object.children, pad + '&nbsp;&nbsp;&nbsp;' );
-
-			}
-
-		} )( scene.children, '&nbsp;&nbsp;&nbsp;' );
-
-		outliner.setOptions( options );
+		var scene = editor.scene;
 
 		if ( scene.fog ) {
 
@@ -203,16 +165,36 @@ Sidebar.Scene = function ( signals ) {
 
 		}
 
-		refreshFogUI();
+		var type = fogType.getValue();
 
-	} );
+		fogColorRow.setDisplay( type === 'None' ? 'none' : '' );
+		fogNearRow.setDisplay( type === 'Fog' ? '' : 'none' );
+		fogFarRow.setDisplay( type === 'Fog' ? '' : 'none' );
+		fogDensityRow.setDisplay( type === 'FogExp2' ? '' : 'none' );
+
+	}
+
+	// events
+
+	var timeout;
+
+	signals.sceneChanged.add( function ( object ) {
+
+    clearTimeout( timeout );
 
-	signals.objectSelected.add( function ( object ) {
+    timeout = setTimeout( function () {
 
-		outliner.setValue( object !== null ? object.id : null );
+      getScene();
+
+    }, 100 );
 
 	} );
 
+
+	signals.fogChanged.add( getFog );
+
+	signals.selected.add( getSelected );
+
 	return container;
 
 }

+ 74 - 0
editor/js/Sidebar.Outliner.Textures.js

@@ -0,0 +1,74 @@
+Sidebar.Outliner.Textures = function ( signals ) {
+
+	var container = new UI.Panel();
+	container.name = "TEX";
+	container.setPadding( '10px' );
+
+	var outliner = new UI.FancySelect().setWidth( '100%' ).setHeight('170px').setColor( '#444' ).setFontSize( '12px' ).onChange( selectFromOutliner );
+	container.add( outliner );
+
+	var textures = null;
+
+  function getTextures() {
+
+		var options = {};
+
+		for ( var i in editor.textures ) {
+
+			var texture = editor.textures[ i ];
+
+			if ( texture.name == '') texture.name = 'Texture' + texture.id;
+
+			options[ i ] = texture.name;
+
+		}
+
+		outliner.setOptions( options );
+    getSelected();
+
+  }
+
+	function selectFromOutliner() {
+
+    var uuid = outliner.getValue();
+
+		editor.select( editor.textures[uuid] );
+
+	}
+
+  function getSelected() {
+
+    var selectedUuids = [];
+
+    for ( var uuid in editor.selected ) {
+
+      if ( editor.textures[uuid] ) selectedUuids.push(uuid);
+
+    }
+
+    // TODO: implement multiple selection
+    outliner.setValue( selectedUuids.length ? selectedUuids[0] : null );
+
+  }
+
+	// events
+
+  var timeout;
+
+	signals.sceneChanged.add( function () {
+
+    clearTimeout( timeout );
+
+    timeout = setTimeout( function () {
+
+      getTextures();
+
+    }, 100 );
+
+	} );
+
+  signals.selected.add( getSelected );
+
+	return container;
+
+}

+ 12 - 0
editor/js/Sidebar.Outliner.js

@@ -0,0 +1,12 @@
+Sidebar.Outliner = function ( signals ) {
+
+  var container = new UI.TabbedPanel();
+
+  container.add( new Sidebar.Outliner.Scene( signals ) );
+  container.add( new Sidebar.Outliner.Geometries( signals ) );
+  container.add( new Sidebar.Outliner.Materials( signals ) );
+  container.add( new Sidebar.Outliner.Textures( signals ) );
+
+  return container;
+
+}

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

@@ -57,7 +57,7 @@ Sidebar.Renderer = function ( signals ) {
 			clearColor: clearColor.getHexValue(),
 			clearAlpha: 1
 		} );
-		signals.rendererChanged.dispatch( renderer );
+		signals.setRenderer.dispatch( renderer );
 
 	}
 

+ 11 - 0
editor/js/Sidebar.Selected.js

@@ -0,0 +1,11 @@
+Sidebar.Selected = function ( signals ) {
+
+  var container = new UI.Panel();
+
+  container.add( new Sidebar.Object3D( signals ) );
+  container.add( new Sidebar.Geometry( signals ) );
+  container.add( new Sidebar.Material( signals ) );
+
+  return container;
+
+}

+ 4 - 6
editor/js/Sidebar.js

@@ -1,15 +1,13 @@
-var Sidebar = function ( signals ) {
+var Sidebar = function ( editor, signals ) {
 
 	var container = new UI.Panel();
 	container.setPosition( 'absolute' );
 	container.setClass( 'sidebar' );
 
 	container.add( new Sidebar.Renderer( signals ) );
-	container.add( new Sidebar.Scene( signals ) );
-	container.add( new Sidebar.Object3D( signals ) );
-	container.add( new Sidebar.Geometry( signals ) );
-	container.add( new Sidebar.Material( signals ) );
-	container.add( new Sidebar.Animation( signals ) );
+  container.add( new Sidebar.Outliner( signals ) );
+  container.add( new Sidebar.Selected( signals ) );
+  container.add( new Sidebar.Animation( signals ) );
 
 	return container;
 

+ 4 - 4
editor/js/Toolbar.js

@@ -1,4 +1,4 @@
-var Toolbar = function ( signals ) {
+var Toolbar = function ( editor, signals ) {
 
 	var container = new UI.Panel();
 	container.setPosition( 'absolute' );
@@ -12,21 +12,21 @@ var Toolbar = function ( signals ) {
 
 	var translate = new UI.Button( 'translate' ).onClick( function () {
 
-		signals.transformModeChanged.dispatch( 'translate' );
+		signals.setTransformMode.dispatch( 'translate' );
 
 	} );
 	buttons.add( translate );
 
 	var rotate = new UI.Button( 'rotate' ).onClick( function () {
 
-		signals.transformModeChanged.dispatch( 'rotate' );
+		signals.setTransformMode.dispatch( 'rotate' );
 
 	} );
 	buttons.add( rotate );
 
 	var scale = new UI.Button( 'scale' ).onClick( function () {
 
-		signals.transformModeChanged.dispatch( 'scale' );
+		signals.setTransformMode.dispatch( 'scale' );
 
 	} );
 	buttons.add( scale );

+ 78 - 308
editor/js/Viewport.js

@@ -1,4 +1,4 @@
-var Viewport = function ( signals ) {
+var Viewport = function ( editor, signals ) {
 
 	var container = new UI.Panel();
 	container.setPosition( 'absolute' );
@@ -12,26 +12,22 @@ var Viewport = function ( signals ) {
 	info.setColor( '#ffffff' );
 	container.add( info );
 
-	var clearColor = 0xAAAAAA;
-	var objects = [];
-
-	// helpers
+	var scene = editor.scene;
+	var sceneHelpers = editor.sceneHelpers;
 
-	var helpersToObjects = {};
-	var objectsToHelpers = {};
-
-	var sceneHelpers = new THREE.Scene();
+	var clearColor = 0xAAAAAA;
 
 	var grid = new THREE.GridHelper( 500, 25 );
 	sceneHelpers.add( grid );
 
 	//
 
-	var scene = new THREE.Scene();
-
 	var camera = new THREE.PerspectiveCamera( 50, container.dom.offsetWidth / container.dom.offsetHeight, 1, 5000 );
 	camera.position.set( 500, 250, 500 );
 	camera.lookAt( scene.position );
+	camera.uuid = "persp";
+
+	editor.select( camera );
 
 	//
 
@@ -41,30 +37,26 @@ var Viewport = function ( signals ) {
 	selectionBox.visible = false;
 	sceneHelpers.add( selectionBox );
 
+	//
+
 	var transformControls = new THREE.TransformControls( camera, container.dom );
 	transformControls.addEventListener( 'change', function () {
 
-		signals.objectChanged.dispatch( selected );
+		signals.objectChanged.dispatch( this.object );
 
 	} );
 	sceneHelpers.add( transformControls.gizmo );
 	transformControls.hide();
 
-	// fog
+	//
 
-	var oldFogType = "None";
-	var oldFogColor = 0xaaaaaa;
-	var oldFogNear = 1;
-	var oldFogFar = 5000;
-	var oldFogDensity = 0.00025;
+	var selected;
 
 	// object picking
 
 	var ray = new THREE.Raycaster();
 	var projector = new THREE.Projector();
 
-	var selected = camera;
-
 	// events
 
 	var getIntersects = function ( event, object ) {
@@ -113,33 +105,27 @@ var Viewport = function ( signals ) {
 
 		if ( onMouseDownPosition.distanceTo( onMouseUpPosition ) < 1 ) {
 
-			var intersects = getIntersects( event, objects );
+			var hit;
 
-			if ( intersects.length > 0 ) {
+			var intersect = getIntersects( event, [ scene, sceneHelpers ] );
 
-				selected = intersects[ 0 ].object;
+			for ( var i in intersect ) {
 
-				if ( helpersToObjects[ selected.id ] !== undefined ) {
+				if ( editor.objects[ intersect[i].object.uuid ] ) {
 
-					selected = helpersToObjects[ selected.id ];
+					editor.selectByUuid( intersect[i].object.uuid );
+					hit = true;
+					break;
 
 				}
 
-				signals.objectSelected.dispatch( selected );
-
-			} else {
-
-				selected = camera;
-
-				signals.objectSelected.dispatch( selected );
-
 			}
 
-			render();
+			if ( !hit ) editor.deselectAll();
 
 		}
 
-		controls.enabled = false;
+		controls.enabled = false; // ?
 
 		document.removeEventListener( 'mouseup', onMouseUp );
 
@@ -147,13 +133,21 @@ var Viewport = function ( signals ) {
 
 	var onDoubleClick = function ( event ) {
 
-		var intersects = getIntersects( event, objects );
+		var intersect = getIntersects( event, [ scene, sceneHelpers ] );
 
-		if ( intersects.length > 0 && intersects[ 0 ].object === selected ) {
+			for ( var i in intersect ) {
 
-			controls.focus( selected );
+				if ( editor.objects[ intersect[i].object.uuid ] ) {
 
-		}
+					editor.selectByUuid( intersect[i].object.uuid );
+
+					controls.focus( editor.objects[ intersect[i].object.uuid ] );
+
+					break;
+
+				}
+
+			}
 
 	};
 
@@ -172,9 +166,23 @@ var Viewport = function ( signals ) {
 	} );
 	controls.enabled = false;
 
+
+	function updateHelpers( object ) {
+
+		if ( object.geometry !== undefined ) {
+
+			selectionBox.update( object );
+			transformControls.update();
+
+		}
+
+		if ( editor.helpers[ object.uuid ] ) editor.helpers[ object.uuid ].update();
+
+	} 
+
 	// signals
 
-	signals.transformModeChanged.add( function ( mode ) {
+	signals.setTransformMode.add( function ( mode ) {
 
 		transformControls.setMode( mode );
 		render();
@@ -187,13 +195,7 @@ var Viewport = function ( signals ) {
 
 	} );
 
-	signals.snapChanged.add( function ( dist ) {
-
-		snapDist = dist;
-
-	} );
-
-	signals.rendererChanged.add( function ( object ) {
+	signals.setRenderer.add( function ( object ) {
 
 		container.dom.removeChild( renderer.domElement );
 
@@ -209,114 +211,20 @@ var Viewport = function ( signals ) {
 
 	} );
 
-	signals.sceneAdded.add( function ( object ) {
-
-		scene.userData = JSON.parse( JSON.stringify( object.userData ) );
-
-		while ( object.children.length > 0 ) {
-
-			signals.objectAdded.dispatch( object.children[ 0 ] );
-
-		}
-
-	} );
-
-	signals.objectAdded.add( function ( object ) {
-
-		// handle children
-
-		object.traverse( function ( object ) {
-
-			// create helpers for invisible object types (lights, cameras, targets)
-
-			if ( object instanceof THREE.PointLight ) {
-
-				var helper = new THREE.PointLightHelper( object, 10 );
-				sceneHelpers.add( helper );
-
-				objectsToHelpers[ object.id ] = helper;
-				helpersToObjects[ helper.lightSphere.id ] = object;
-
-				objects.push( helper.lightSphere );
-
-			} else if ( object instanceof THREE.DirectionalLight ) {
-
-				var helper = new THREE.DirectionalLightHelper( object, 10 );
-				sceneHelpers.add( helper );
-
-				objectsToHelpers[ object.id ] = helper;
-				helpersToObjects[ helper.lightSphere.id ] = object;
-
-				objects.push( helper.lightSphere );
-
-			} else if ( object instanceof THREE.SpotLight ) {
-
-				var helper = new THREE.SpotLightHelper( object, 10 );
-				sceneHelpers.add( helper );
-
-				objectsToHelpers[ object.id ] = helper;
-				helpersToObjects[ helper.lightSphere.id ] = object;
-
-				objects.push( helper.lightSphere );
-
-			} else if ( object instanceof THREE.HemisphereLight ) {
-
-				var helper = new THREE.HemisphereLightHelper( object, 10 );
-				sceneHelpers.add( helper );
-
-				objectsToHelpers[ object.id ] = helper;
-				helpersToObjects[ helper.lightSphere.id ] = object;
-
-				objects.push( helper.lightSphere );
-
-			} else {
-
-				// add to picking list
-
-				objects.push( object );
-
-			}
-
-		} );
-
-		scene.add( object );
-
-		// TODO: Add support for hierarchies with lights
-
-		if ( object instanceof THREE.Light )  {
-
-			updateMaterials( scene );
-
-		}
-
-		updateInfo();
-
-		signals.sceneChanged.dispatch( scene );
-		signals.objectSelected.dispatch( object );
-
-	} );
-
-	signals.objectSelected.add( function ( object ) {
+	signals.selected.add( function () {
 
 		selectionBox.visible = false;
 		transformControls.detach();
 
-		if ( object !== null ) {
-
-			if ( object.geometry !== undefined ) {
+		selected = editor.listSelected( 'object' );
+		object = ( selected.length ) ? selected[0] : null;
 
-				selectionBox.update( object );
-				selectionBox.visible = true;
+		if ( object && object !== scene ) {
 
-			}
-
-			selected = object;
-
-			if ( selected instanceof THREE.PerspectiveCamera === false ) {
+			selectionBox.visible = true;
+			transformControls.attach( object );
 
-				transformControls.attach(object);
-
-			}
+			updateHelpers( object );
 
 		}
 
@@ -324,157 +232,58 @@ var Viewport = function ( signals ) {
 
 	} );
 
-	signals.objectChanged.add( function ( object ) {
-
-		if ( object.geometry !== undefined ) {
-
-			selectionBox.update( object );
-			transformControls.update();
-			updateInfo();
-
-		}
-
-		if ( objectsToHelpers[ object.id ] !== undefined ) {
-
-			objectsToHelpers[ object.id ].update();
-
-		}
+	signals.sceneChanged.add( function () {
 
 		render();
 
-		signals.sceneChanged.dispatch( scene );
-
 	} );
 
-	signals.cloneSelectedObject.add( function () {
-
-		if ( selected === camera ) return;
-
-		var object = selected.clone();
+	signals.objectAdded.add( function ( object ) {
 
-		signals.objectAdded.dispatch( object );
+		updateHelpers( object );
+		updateInfo();
+		render();
 
 	} );
 
-	signals.removeSelectedObject.add( function () {
-
-		if ( selected.parent === undefined ) return;
-
-		var name = selected.name ?  '"' + selected.name + '"': "selected object";
-
-		if ( confirm( 'Delete ' + name + '?' ) === false ) return;
-
-		var parent = selected.parent;
-
-		if ( selected instanceof THREE.PointLight ||
-		     selected instanceof THREE.DirectionalLight ||
-		     selected instanceof THREE.SpotLight ||
-		     selected instanceof THREE.HemisphereLight ) {
-
-			var helper = objectsToHelpers[ selected.id ];
-
-			objects.splice( objects.indexOf( helper.lightSphere ), 1 );
-
-			helper.parent.remove( helper );
-			selected.parent.remove( selected );
-
-			delete objectsToHelpers[ selected.id ];
-			delete helpersToObjects[ helper.id ];
-
-			if ( selected instanceof THREE.DirectionalLight ||
-			     selected instanceof THREE.SpotLight ) {
-
-				selected.target.parent.remove( selected.target );
-
-			}
-
-			updateMaterials( scene );
-
-		} else {
-
-			selected.traverse( function ( object ) {
-
-				var index = objects.indexOf( object );
-
-				if ( index !== -1 ) {
-
-					objects.splice( index, 1 )
-
-				}
-
-			} );
-
-			selected.parent.remove( selected );
-
-			updateInfo();
-
-		}
+	signals.objectChanged.add( function ( object ) {
 
-		signals.sceneChanged.dispatch( scene );
-		signals.objectSelected.dispatch( parent );
+		updateHelpers( object );
+		updateInfo();
+		render();
 
 	} );
 
-	signals.materialChanged.add( function ( material ) {
+	signals.objectDeleted.add( function () {
 
+		updateInfo();
 		render();
 
 	} );
 
-	signals.clearColorChanged.add( function ( color ) {
+	signals.materialChanged.add( function ( material ) {
 
-		renderer.setClearColor( color );
 		render();
 
-		clearColor = color;
-
 	} );
 
-	signals.fogTypeChanged.add( function ( fogType ) {
-
-		if ( fogType !== oldFogType ) {
-
-			if ( fogType === "None" ) {
-
-				scene.fog = null;
-
-			} else if ( fogType === "Fog" ) {
-
-				scene.fog = new THREE.Fog( oldFogColor, oldFogNear, oldFogFar );
-
-			} else if ( fogType === "FogExp2" ) {
-
-				scene.fog = new THREE.FogExp2( oldFogColor, oldFogDensity );
-
-			}
-
-			updateMaterials( scene );
-
-			oldFogType = fogType;
-
-		}
+	signals.geometryChanged.add( function ( material ) {
 
+		updateInfo();
 		render();
 
 	} );
 
-	signals.fogColorChanged.add( function ( fogColor ) {
-
-		oldFogColor = fogColor;
-
-		updateFog( scene );
+	signals.clearColorChanged.add( function ( color ) {
 
+		renderer.setClearColor( color );
 		render();
 
-	} );
-
-	signals.fogParametersChanged.add( function ( near, far, density ) {
+		clearColor = color;
 
-		oldFogNear = near;
-		oldFogFar = far;
-		oldFogDensity = density;
+	} );
 
-		updateFog( scene );
+	signals.fogChanged.add( function () {
 
 		render();
 
@@ -492,22 +301,21 @@ var Viewport = function ( signals ) {
 	} );
 
 	signals.playAnimations.add( function (animations) {
-		
+
 		function animate() {
 			requestAnimationFrame( animate );
-			
+
 			for (var i = 0; i < animations.length ; i++ ){
 				animations[i].update(0.016);
 			} 
 
-
 			render();
 		}
 
 		animate();
 
 	} );
-
+	
 	//
 
 	var renderer;
@@ -553,44 +361,6 @@ var Viewport = function ( signals ) {
 
 	}
 
-	function updateMaterials( root ) {
-
-		root.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 ) {
-
-		if ( root.fog ) {
-
-			root.fog.color.setHex( oldFogColor );
-
-			if ( root.fog.near !== undefined ) root.fog.near = oldFogNear;
-			if ( root.fog.far !== undefined ) root.fog.far = oldFogFar;
-			if ( root.fog.density !== undefined ) root.fog.density = oldFogDensity;
-
-		}
-
-	}
-
 	function animate() {
 
 		requestAnimationFrame( animate );

+ 97 - 24
editor/js/libs/ui.js

@@ -12,6 +12,14 @@ UI.Element.prototype = {
 
 	},
 
+	setId: function ( name ) {
+
+		this.dom.id = name;
+
+		return this;
+
+	},
+
 	setStyle: function ( style, array ) {
 
 		for ( var i = 0; i < array.length; i ++ ) {
@@ -84,6 +92,7 @@ UI.Panel = function () {
 	this.dom = dom;
 
 	return this;
+
 };
 
 UI.Panel.prototype = Object.create( UI.Element.prototype );
@@ -100,7 +109,6 @@ UI.Panel.prototype.add = function () {
 
 };
 
-
 UI.Panel.prototype.remove = function () {
 
 	for ( var i = 0; i < arguments.length; i ++ ) {
@@ -113,6 +121,73 @@ UI.Panel.prototype.remove = function () {
 
 };
 
+// Tabbed Panel
+
+UI.TabbedPanel = function () {
+
+	UI.Panel.call( this );
+
+	var header = new UI.Panel();
+	header.setBorderBottom( '1px solid #ccc' );
+
+	this.dom.appendChild( header.dom );
+	
+	this.header = header.dom;
+
+	this.tabs = [];
+	this.contents = [];
+
+	return this;
+
+};
+
+UI.TabbedPanel.prototype = Object.create( UI.Panel.prototype );
+
+UI.TabbedPanel.prototype.add = function () {
+
+	for ( var i = 0; i < arguments.length; i ++ ) {
+
+		var tab = new UI.Text( arguments[ i ].name ).setColor( '#666' );
+		tab.setPadding( '5px 10px' );
+		tab.setBorder( '1px solid #ccc' );
+		tab.setMargin( '0 0 -1px 5px' );
+		this.header.appendChild( tab.dom );
+
+		var content = arguments[ i ];
+		this.dom.appendChild( content.dom );
+		content.setDisplay( 'none' );
+
+		this.tabs.push(tab);
+		this.contents.push(content);
+
+		var scope = this;
+
+		tab.onClick( function() {
+
+			for (var j in scope.contents ){
+				scope.contents[j].setDisplay( 'none' );
+				scope.tabs[j].setBorderBottom( '1px solid #ccc' );
+			}
+			content.setDisplay( ' block ');
+			tab.setBorderBottom( '1px solid #eee' );
+
+		} );
+
+	}
+
+	this.contents[0].setDisplay( 'block' );
+	this.tabs[0].setBorderBottom( '1px solid #eee' );
+
+	return this;
+
+};
+
+UI.TabbedPanel.prototype.remove = function () {
+
+	return this;
+
+};
+
 // Text
 
 UI.Text = function ( text ) {
@@ -324,7 +399,7 @@ UI.FancySelect = function () {
 	this.dom = dom;
 
 	this.options = [];
-	this.selectedValue = null;
+	this.selectedKeys = null;
 
 	return this;
 
@@ -347,19 +422,11 @@ UI.FancySelect.prototype.setOptions = function ( options ) {
 
 	scope.options = [];
 
-	var generateOptionCallback = function ( element, value ) {
+	var generateOptionCallback = function ( element, key ) {
 
 		return function ( event ) {
 
-			for ( var i = 0; i < scope.options.length; i ++ ) {
-
-				scope.options[ i ].style.backgroundColor = '#f0f0f0';
-
-			}
-
-			element.style.backgroundColor = '#f0f0f0';
-
-			scope.selectedValue = value;
+			scope.selectedKeys = [key];
 
 			scope.dom.dispatchEvent( changeEvent );
 
@@ -387,34 +454,40 @@ UI.FancySelect.prototype.setOptions = function ( options ) {
 
 UI.FancySelect.prototype.getValue = function () {
 
-	return this.selectedValue;
+	return this.selectedKeys;
 
 };
 
-UI.FancySelect.prototype.setValue = function ( value ) {
+UI.FancySelect.prototype.setValue = function ( keys ) {
 
-	// must convert raw value into string for compatibility with UI.Select
-	// which uses string values (initialized from options keys)
+	// must convert raw keys into string for compatibility with UI.Select
+	// which uses string keys (initialized from options keys)
 
-	var key = value ? value.toString() : value;
+	keys = ( keys instanceof Array ) ? keys : [keys];
 
 	for ( var i = 0; i < this.options.length; i ++ ) {
 
-		var element = this.options[ i ];
+		this.options[ i ].style.backgroundColor = '';
 
-		if ( element.value === key ) {
+	}
+
+	for ( var i in keys ) {
+
+		var key = keys[ i ] ? keys[ i ].toString() : keys[ i ];
 
-			element.style.backgroundColor = '#f0f0f0';
+		for ( var j = 0; j < this.options.length; j ++ ) {
 
-		} else {
+			if ( this.options[ j ].value === key ) {
 
-			element.style.backgroundColor = '';
+				this.options[ j ].style.backgroundColor = '#f0f0f0';
+
+			}
 
 		}
 
 	}
 
-	this.selectedValue = value;
+	this.selectedKeys = keys;
 
 	return this;
 
@@ -888,4 +961,4 @@ UI.Button.prototype.setLabel = function ( value ) {
 
 	return this;
 
-};
+};

+ 1 - 0
examples/js/exporters/MaterialExporter.js

@@ -19,6 +19,7 @@ THREE.MaterialExporter.prototype = {
 		};
 
 		if ( material.name !== "" ) output.name = material.name;
+        if ( material.uuid !== "" ) output.uuid = material.uuid;
 
 		if ( material instanceof THREE.MeshBasicMaterial ) {
 

+ 2 - 0
examples/js/exporters/ObjectExporter.js

@@ -40,6 +40,7 @@ THREE.ObjectExporter.prototype = {
 				var data = {};
 
 				if ( geometry.name !== "" ) data.name = geometry.name;
+        if ( geometry.uuid !== "" ) data.uuid = geometry.uuid;
 
 				if ( geometry instanceof THREE.PlaneGeometry ) {
 
@@ -159,6 +160,7 @@ THREE.ObjectExporter.prototype = {
 			var data = {};
 
 			if ( object.name !== '' ) data.name = object.name;
+			if ( object.uuid !== '' ) data.uuid = object.uuid;
 			if ( JSON.stringify( object.userData ) !== '{}' ) data.userData = object.userData;
 			if ( object.visible !== true ) data.visible = object.visible;
 

+ 87 - 69
examples/js/loaders/ObjectLoader.js

@@ -45,17 +45,28 @@ THREE.ObjectLoader.prototype = {
 
 	parse: function ( json ) {
 
-		// geometries
+		var geometries = this.parseGeometries( json.geometries );
 
-		if ( json.geometries !== undefined ) {
+		var materials = this.parseMaterials( json.materials );
+
+		var object = this.parseObject( json.object, geometries, materials );
+
+		return object;
+
+	},
+
+	parseGeometries: function ( json ) {
+
+		var geometries = [];
+
+		if ( json !== undefined ) {
 
-			var geometries = [];
 			var loader = new THREE.JSONLoader();
 
-			for ( var i = 0, l = json.geometries.length; i < l; i ++ ) {
+			for ( var i = 0, l = json.length; i < l; i ++ ) {
 
 				var geometry;
-				var data = json.geometries[ i ];
+				var data = json[ i ];
 
 				switch ( data.type ) {
 
@@ -154,25 +165,32 @@ THREE.ObjectLoader.prototype = {
 				}
 
 				if ( data.name !== undefined ) geometry.name = data.name;
+				if ( data.uuid !== undefined ) geometry.uuid = data.uuid;
 				geometries.push( geometry );
 
 			}
 
 		}
 
-		// materials
+		return geometries;
+
+	},
 
-		if ( json.materials !== undefined ) {
+	parseMaterials: function ( json ) {
+
+		var materials = [];
+
+		if ( json !== undefined ) {
 
-			var materials = [];
 			var loader = new THREE.MaterialLoader();
 
-			for ( var i = 0, l = json.materials.length; i < l; i ++ ) {
+			for ( var i = 0, l = json.length; i < l; i ++ ) {
 
-				var data = json.materials[ i ];
+				var data = json[ i ];
 				var material = loader.parse( data );
 
 				if ( data.name !== undefined ) material.name = data.name;
+				if ( data.uuid !== undefined ) material.uuid = data.uuid;
 
 				materials.push( material );
 
@@ -180,108 +198,108 @@ THREE.ObjectLoader.prototype = {
 
 		}
 
-		// objects
+		return materials;
 
-		var parseObject = function ( data ) {
+	},
 
-			var object;
+	parseObject: function ( data, geometries, materials ) {
 
-			switch ( data.type ) {
+		var object;
 
-				case 'Scene':
+		switch ( data.type ) {
 
-					object = new THREE.Scene();
+			case 'Scene':
 
-					break;
+				object = new THREE.Scene();
 
-				case 'PerspectiveCamera':
+				break;
 
-					object = new THREE.PerspectiveCamera( data.fov, data.aspect, data.near, data.far );
-					object.position.fromArray( data.position );
-					object.rotation.fromArray( data.rotation );
+			case 'PerspectiveCamera':
 
-					break;
+				object = new THREE.PerspectiveCamera( data.fov, data.aspect, data.near, data.far );
+				object.position.fromArray( data.position );
+				object.rotation.fromArray( data.rotation );
 
-				case 'OrthographicCamera':
+				break;
 
-					object = new THREE.OrthographicCamera( data.left, data.right, data.top, data.bottom, data.near, data.far );
-					object.position.fromArray( data.position );
-					object.rotation.fromArray( data.rotation );
+			case 'OrthographicCamera':
 
-					break;
+				object = new THREE.OrthographicCamera( data.left, data.right, data.top, data.bottom, data.near, data.far );
+				object.position.fromArray( data.position );
+				object.rotation.fromArray( data.rotation );
 
-				case 'AmbientLight':
+				break;
 
-					object = new THREE.AmbientLight( data.color );
+			case 'AmbientLight':
 
-					break;
+				object = new THREE.AmbientLight( data.color );
 
-				case 'DirectionalLight':
+				break;
 
-					object = new THREE.DirectionalLight( data.color, data.intensity );
-					object.position.fromArray( data.position );
+			case 'DirectionalLight':
 
-					break;
+				object = new THREE.DirectionalLight( data.color, data.intensity );
+				object.position.fromArray( data.position );
 
-				case 'PointLight':
+				break;
 
-					object = new THREE.PointLight( data.color, data.intensity, data.distance );
-					object.position.fromArray( data.position );
+			case 'PointLight':
 
-					break;
+				object = new THREE.PointLight( data.color, data.intensity, data.distance );
+				object.position.fromArray( data.position );
 
-				case 'SpotLight':
+				break;
 
-					object = new THREE.SpotLight( data.color, data.intensity, data.distance, data.angle, data.exponent );
-					object.position.fromArray( data.position );
+			case 'SpotLight':
 
-					break;
+				object = new THREE.SpotLight( data.color, data.intensity, data.distance, data.angle, data.exponent );
+				object.position.fromArray( data.position );
 
-				case 'HemisphereLight':
+				break;
 
-					object = new THREE.HemisphereLight( data.color, data.groundColor, data.intensity );
-					object.position.fromArray( data.position );
+			case 'HemisphereLight':
 
-					break;
+				object = new THREE.HemisphereLight( data.color, data.groundColor, data.intensity );
+				object.position.fromArray( data.position );
 
-				case 'Mesh':
+				break;
 
-					object = new THREE.Mesh( geometries[ data.geometry ], materials[ data.material ] );
-					object.position.fromArray( data.position );
-					object.rotation.fromArray( data.rotation );
-					object.scale.fromArray( data.scale );
+			case 'Mesh':
 
-					break;
+				object = new THREE.Mesh( geometries[ data.geometry ], materials[ data.material ] );
+				object.position.fromArray( data.position );
+				object.rotation.fromArray( data.rotation );
+				object.scale.fromArray( data.scale );
 
-				default:
+				break;
 
-					object = new THREE.Object3D();
-					object.position.fromArray( data.position );
-					object.rotation.fromArray( data.rotation );
-					object.scale.fromArray( data.scale );
+			default:
 
-			}
+				object = new THREE.Object3D();
+				object.position.fromArray( data.position );
+				object.rotation.fromArray( data.rotation );
+				object.scale.fromArray( data.scale );
 
-			if ( data.name !== undefined ) object.name = data.name;
-			if ( data.visible !== undefined ) object.visible = data.visible;
-			if ( data.userData !== undefined ) object.userData = data.userData;
+		}
 
-			if ( data.children !== undefined ) {
+		if ( data.name !== undefined ) object.name = data.name;
+	  if ( data.uuid !== undefined ) object.uuid = data.uuid;
+		if ( data.visible !== undefined ) object.visible = data.visible;
+		if ( data.userData !== undefined ) object.userData = data.userData;
 
-				for ( var i = 0, l = data.children.length; i < l; i ++ ) {
+		if ( data.children !== undefined ) {
 
-					object.add( parseObject( data.children[ i ] ) );
+			for ( var i = 0, l = data.children.length; i < l; i ++ ) {
 
-				}
+				object.add( this.parseObject( data.children[ i ], geometries, materials ) );
 
 			}
 
-			return object;
-
 		}
 
-		return parseObject( json.object );
+
+		return object;
 
 	}
 
-};
+};

+ 1 - 0
src/core/Geometry.js

@@ -12,6 +12,7 @@ THREE.Geometry = function () {
 	this.id = THREE.GeometryIdCount ++;
 
 	this.name = '';
+	this.uuid = '';
 
 	this.vertices = [];
 	this.colors = [];  // one-to-one vertex colors, used in ParticleSystem, Line and Ribbon

+ 10 - 4
src/core/Object3D.js

@@ -10,6 +10,7 @@ THREE.Object3D = function () {
 	this.id = THREE.Object3DIdCount ++;
 
 	this.name = '';
+	this.uuid = '';
 
 	this.parent = undefined;
 	this.children = [];
@@ -442,9 +443,10 @@ THREE.Object3D.prototype = {
 
 	},
 
-	clone: function ( object ) {
+	clone: function ( object, recursive ) {
 
 		if ( object === undefined ) object = new THREE.Object3D();
+		if ( recursive === undefined ) recursive = true;
 
 		object.name = this.name;
 
@@ -477,10 +479,14 @@ THREE.Object3D.prototype = {
 
 		object.userData = JSON.parse( JSON.stringify( this.userData ) );
 
-		for ( var i = 0; i < this.children.length; i ++ ) {
+		if ( recursive ) {
+		
+			for ( var i = 0; i < this.children.length; i ++ ) {
 
-			var child = this.children[ i ];
-			object.add( child.clone() );
+				var child = this.children[ i ];
+				object.add( child.clone() );
+
+			}
 
 		}
 

+ 1 - 0
src/materials/Material.js

@@ -8,6 +8,7 @@ THREE.Material = function () {
 	this.id = THREE.MaterialIdCount ++;
 
 	this.name = '';
+	this.uuid = '';
 
 	this.side = THREE.FrontSide;
 

+ 2 - 1
src/scenes/Fog.js

@@ -6,7 +6,8 @@
 THREE.Fog = function ( hex, near, far ) {
 
 	this.name = '';
-
+  this.uuid = '';
+  
 	this.color = new THREE.Color( hex );
 
 	this.near = ( near !== undefined ) ? near : 1;

+ 2 - 0
src/scenes/FogExp2.js

@@ -6,6 +6,8 @@
 THREE.FogExp2 = function ( hex, density ) {
 
 	this.name = '';
+  this.uuid = '';
+  
 	this.color = new THREE.Color( hex );
 	this.density = ( density !== undefined ) ? density : 0.00025;
 

+ 1 - 0
src/textures/Texture.js

@@ -9,6 +9,7 @@ THREE.Texture = function ( image, mapping, wrapS, wrapT, magFilter, minFilter, f
 	this.id = THREE.TextureIdCount ++;
 
 	this.name = '';
+	this.uuid = '';
 
 	this.image = image;
 	this.mipmaps = [];