Procházet zdrojové kódy

added outliner for multiple asset types

added editor class and few methods

removed uuid

Implemented more methods

editor refactoring

editor refactoring

editor api improvements

finished editor API

hooked up editor to UI as an argument
Aleksandar Rodic před 12 roky
rodič
revize
0e9c555e3a
39 změnil soubory, kde provedl 2221 přidání a 901 odebrání
  1. 78 22
      editor/index.html
  2. 965 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. 24 9
      editor/js/Sidebar.Geometry.CubeGeometry.js
  11. 24 9
      editor/js/Sidebar.Geometry.CylinderGeometry.js
  12. 24 9
      editor/js/Sidebar.Geometry.IcosahedronGeometry.js
  13. 24 9
      editor/js/Sidebar.Geometry.PlaneGeometry.js
  14. 24 9
      editor/js/Sidebar.Geometry.SphereGeometry.js
  15. 24 10
      editor/js/Sidebar.Geometry.TorusGeometry.js
  16. 24 9
      editor/js/Sidebar.Geometry.TorusKnotGeometry.js
  17. 26 24
      editor/js/Sidebar.Geometry.js
  18. 85 30
      editor/js/Sidebar.Material.js
  19. 163 79
      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>

+ 965 - 0
editor/js/Editor.js

@@ -0,0 +1,965 @@
+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 tube = parameters.tube ? parameters.tube : null;
+    var arc = parameters.arc ? parameters.arc : null;
+    var detail = parameters.detail ? parameters.detail : 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;
+
+      geometry = new THREE.SphereGeometry( radius, widthSegments, heightSegments );
+      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;
+      var p = 2;
+      var q = 3;
+      var 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;
+
+        }
+
+      }
+
+    }
+
+  },
+
+  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;
 
 }

+ 24 - 9
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,11 +68,11 @@ Sidebar.Geometry.CubeGeometry = function ( signals, object ) {
 
 	function update() {
 
-		delete object.__webglInit; // TODO: Remove hack (WebGLRenderer refactoring)
-
-		object.geometry.dispose();
+		var uuid = geometry.uuid;
+		var name = geometry.name;
+		var object;
 
-		object.geometry = new THREE.CubeGeometry(
+		editor.geometries[uuid] = new THREE.CubeGeometry(
 			width.getValue(),
 			height.getValue(),
 			depth.getValue(),
@@ -83,9 +81,26 @@ Sidebar.Geometry.CubeGeometry = function ( signals, object ) {
 			depthSegments.getValue()
 		);
 
-		object.geometry.computeBoundingSphere();
+		editor.geometries[uuid].computeBoundingSphere();
+		editor.geometries[uuid].uuid = uuid;
+		editor.geometries[uuid].name = name;
+
+		for ( var i in editor.objects ) {
+
+			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 );
+
+			}
 
-		signals.objectChanged.dispatch( object );
+		}
 
 	}
 

+ 24 - 9
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();
@@ -70,11 +68,11 @@ Sidebar.Geometry.CylinderGeometry = function ( signals, object ) {
 
 	function update() {
 
-		delete object.__webglInit; // TODO: Remove hack (WebGLRenderer refactoring)
-
-		object.geometry.dispose();
+		var uuid = geometry.uuid;
+		var name = geometry.name;
+		var object;
 
-		object.geometry = new THREE.CylinderGeometry(
+		editor.geometries[uuid] = new THREE.CylinderGeometry(
 			radiusTop.getValue(),
 			radiusBottom.getValue(),
 			height.getValue(),
@@ -83,9 +81,26 @@ Sidebar.Geometry.CylinderGeometry = function ( signals, object ) {
 			openEnded.getValue()
 		);
 
-		object.geometry.computeBoundingSphere();
+		editor.geometries[uuid].computeBoundingSphere();
+		editor.geometries[uuid].uuid = uuid;
+		editor.geometries[uuid].name = name;
+
+		for ( var i in editor.objects ) {
+
+			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 );
+
+			}
 
-		signals.objectChanged.dispatch( object );
+		}
 
 	}
 

+ 24 - 9
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,18 +29,35 @@ Sidebar.Geometry.IcosahedronGeometry = function ( signals, object ) {
 
 	function update() {
 
-		delete object.__webglInit; // TODO: Remove hack (WebGLRenderer refactoring)
-
-		object.geometry.dispose();
+		var uuid = geometry.uuid;
+		var name = geometry.name;
+		var object;
 
-		object.geometry = new THREE.IcosahedronGeometry(
+		editor.geometries[uuid] = new THREE.IcosahedronGeometry(
 			radius.getValue(),
 			detail.getValue()
 		);
 
-		object.geometry.computeBoundingSphere();
+		editor.geometries[uuid].computeBoundingSphere();
+		editor.geometries[uuid].uuid = uuid;
+		editor.geometries[uuid].name = name;
+
+		for ( var i in editor.objects ) {
+
+			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 );
+
+			}
 
-		signals.objectChanged.dispatch( object );
+		}
 
 	}
 

+ 24 - 9
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,20 +49,37 @@ Sidebar.Geometry.PlaneGeometry = function ( signals, object ) {
 
 	function update() {
 
-		delete object.__webglInit; // TODO: Remove hack (WebGLRenderer refactoring)
-
-		object.geometry.dispose();
+		var uuid = geometry.uuid;
+		var name = geometry.name;
+		var object;
 
-		object.geometry = new THREE.PlaneGeometry(
+		editor.geometries[uuid] = new THREE.PlaneGeometry(
 			width.getValue(),
 			height.getValue(),
 			widthSegments.getValue(),
 			heightSegments.getValue()
 		);
 
-		object.geometry.computeBoundingSphere();
+		editor.geometries[uuid].computeBoundingSphere();
+		editor.geometries[uuid].uuid = uuid;
+		editor.geometries[uuid].name = name;
+
+		for ( var i in editor.objects ) {
+
+			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 );
+
+			}
 
-		signals.objectChanged.dispatch( object );
+		}
 
 	}
 

+ 24 - 9
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,11 +79,11 @@ Sidebar.Geometry.SphereGeometry = function ( signals, object ) {
 
 	function update() {
 
-		delete object.__webglInit; // TODO: Remove hack (WebGLRenderer refactoring)
-
-		object.geometry.dispose();
+		var uuid = geometry.uuid;
+		var name = geometry.name;
+		var object;
 
-		object.geometry = new THREE.SphereGeometry(
+		editor.geometries[uuid] = new THREE.SphereGeometry(
 			radius.getValue(),
 			widthSegments.getValue(),
 			heightSegments.getValue(),
@@ -95,9 +93,26 @@ Sidebar.Geometry.SphereGeometry = function ( signals, object ) {
 			thetaLength.getValue()
 		);
 
-		object.geometry.computeBoundingSphere();
+		editor.geometries[uuid].computeBoundingSphere();
+		editor.geometries[uuid].uuid = uuid;
+		editor.geometries[uuid].name = name;
+
+		for ( var i in editor.objects ) {
+
+			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 );
+
+			}
 
-		signals.objectChanged.dispatch( object );
+		}
 
 	}
 

+ 24 - 10
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,16 +54,15 @@ Sidebar.Geometry.TorusGeometry = function ( signals, object ) {
 
 	container.add( arcRow );
 
-
 	//
 
 	function update() {
 
-		delete object.__webglInit; // TODO: Remove hack (WebGLRenderer refactoring)
-
-		object.geometry.dispose();
+		var uuid = geometry.uuid;
+		var name = geometry.name;
+		var object;
 
-		object.geometry = new THREE.TorusGeometry(
+		editor.geometries[uuid] = new THREE.TorusGeometry(
 			radius.getValue(),
 			tube.getValue(),
 			radialSegments.getValue(),
@@ -73,9 +70,26 @@ Sidebar.Geometry.TorusGeometry = function ( signals, object ) {
 			arc.getValue()
 		);
 
-		object.geometry.computeBoundingSphere();
+		editor.geometries[uuid].computeBoundingSphere();
+		editor.geometries[uuid].uuid = uuid;
+		editor.geometries[uuid].name = name;
+
+		for ( var i in editor.objects ) {
+
+			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 );
+
+			}
 
-		signals.objectChanged.dispatch( object );
+		}
 
 	}
 

+ 24 - 9
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,11 +79,11 @@ Sidebar.Geometry.TorusKnotGeometry = function ( signals, object ) {
 
 	function update() {
 
-		delete object.__webglInit; // TODO: Remove hack (WebGLRenderer refactoring)
-
-		object.geometry.dispose();
+		var uuid = geometry.uuid;
+		var name = geometry.name;
+		var object;
 
-		object.geometry = new THREE.TorusKnotGeometry(
+		editor.geometries[uuid] = new THREE.TorusKnotGeometry(
 			radius.getValue(),
 			tube.getValue(),
 			radialSegments.getValue(),
@@ -95,9 +93,26 @@ Sidebar.Geometry.TorusKnotGeometry = function ( signals, object ) {
 			heightScale.getValue()
 		);
 
-		object.geometry.computeBoundingSphere();
+		editor.geometries[uuid].computeBoundingSphere();
+		editor.geometries[uuid].uuid = uuid;
+		editor.geometries[uuid].name = name;
+
+		for ( var i in editor.objects ) {
+
+			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 );
+
+			}
 
-		signals.objectChanged.dispatch( object );
+		}
 
 	}
 

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

+ 163 - 79
editor/js/Sidebar.Object3D.js

@@ -9,25 +9,49 @@ 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(){  signals.selectObject.dispatch( 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(){  signals.selectGeometry.dispatch( editor.geometries[ objectGeometry.getValue() ] ) } );
+	objectGeometryRow.add( geometryLabel );
+	objectGeometryRow.add( objectGeometry );
 
-	objectNameRow.add( new UI.Text( 'Name' ).setWidth( '90px' ).setColor( '#666' ) );
-	objectNameRow.add( objectName );
+	container.add( objectGeometryRow );
 
-	container.add( objectNameRow );
+	// material
+
+	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(){  signals.selectMaterial.dispatch( editor.materials[ objectMaterial.getValue() ] ) } );
+	objectMaterialRow.add( materialLabel );
+
+	objectMaterialRow.add( objectMaterial );
+
+	container.add( objectMaterialRow );
 
 	// position
 
@@ -193,18 +217,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 +244,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 +259,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 +272,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 +290,7 @@ Sidebar.Object3D = function ( signals ) {
 
 					}
 
-					parent.add( selected );
+					parent.add( object );
 
 					signals.sceneChanged.dispatch( scene );
 
@@ -273,78 +298,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();
 
-			selected.scale.x = objectScaleX.getValue();
-			selected.scale.y = objectScaleY.getValue();
-			selected.scale.z = objectScaleZ.getValue();
+				if ( object.geometry.uuid !== newGeometryUUid && object.uuid !== newGeometryUUid ) {
 
-			if ( selected.fov !== undefined ) {
+					object.geometry = editor.geometries[newGeometryUUid];
 
-				selected.fov = objectFov.getValue();
-				selected.updateProjectionMatrix();
+					// TODO: Update Geometry;
+
+					signals.objectChanged.dispatch( object );
+
+				}
 
 			}
 
-			if ( selected.near !== undefined ) {
+			if ( object.material !== undefined ) {
+
+				var newMaterialUUid = objectMaterial.getValue();
+
+				if ( object.material.uuid !== newMaterialUUid && object.uuid !== newMaterialUUid ) {
 
-				selected.near = objectNear.getValue();
+					object.material = editor.materials[newMaterialUUid];
+
+					signals.objectChanged.dispatch( object );
+
+				}
 
 			}
 
-			if ( selected.far !== undefined ) {
+			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();
+
+			object.scale.x = objectScaleX.getValue();
+			object.scale.y = objectScaleY.getValue();
+			object.scale.z = objectScaleZ.getValue();
 
-				selected.far = objectFar.getValue();
+			if ( object.fov !== undefined ) {
+
+				object.fov = objectFov.getValue();
+				object.updateProjectionMatrix();
 
 			}
 
-			if ( selected.intensity !== undefined ) {
+			if ( object.near !== undefined ) {
 
-				selected.intensity = objectIntensity.getValue();
+				object.near = objectNear.getValue();
 
 			}
 
-			if ( selected.color !== undefined ) {
+			if ( object.far !== undefined ) {
 
-				selected.color.setHex( objectColor.getHexValue() );
+				object.far = objectFar.getValue();
 
 			}
 
-			if ( selected.groundColor !== undefined ) {
+			if ( object.intensity !== undefined ) {
 
-				selected.groundColor.setHex( objectGroundColor.getHexValue() );
+				object.intensity = objectIntensity.getValue();
 
 			}
 
-			if ( selected.distance !== undefined ) {
+			if ( object.color !== undefined ) {
 
-				selected.distance = objectDistance.getValue();
+				object.color.setHex( objectColor.getHexValue() );
 
 			}
 
-			if ( selected.angle !== undefined ) {
+			if ( object.groundColor !== undefined ) {
 
-				selected.angle = objectAngle.getValue();
+				object.groundColor.setHex( objectGroundColor.getHexValue() );
 
 			}
 
-			if ( selected.exponent !== undefined ) {
+			if ( object.distance !== undefined ) {
 
-				selected.exponent = objectExponent.getValue();
+				object.distance = objectDistance.getValue();
 
 			}
 
-			selected.visible = objectVisible.getValue();
+			if ( object.angle !== undefined ) {
+
+				object.angle = objectAngle.getValue();
+
+			}
+
+			if ( object.exponent !== undefined ) {
+
+				object.exponent = objectExponent.getValue();
+
+			}
+
+			object.visible = objectVisible.getValue();
 
 			try {
 
-				selected.userData = JSON.parse( objectUserData.getValue() );
+				object.userData = JSON.parse( objectUserData.getValue() );
 
 			} catch ( error ) {
 
@@ -352,7 +408,7 @@ Sidebar.Object3D = function ( signals ) {
 
 			}
 
-			signals.objectChanged.dispatch( selected );
+			signals.objectChanged.dispatch( object );
 
 		}
 
@@ -362,6 +418,8 @@ Sidebar.Object3D = function ( signals ) {
 
 		var properties = {
 			'parent': objectParentRow,
+			'geometry': objectGeometryRow,
+			'material': objectMaterialRow,
 			'fov': objectFovRow,
 			'near': objectNearRow,
 			'far': objectFarRow,
@@ -375,7 +433,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 +441,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 +481,81 @@ Sidebar.Object3D = function ( signals ) {
 
 	// events
 
-	signals.sceneChanged.add( function ( object ) {
+	signals.sceneChanged.add( function () {
 
-		scene = object;
 
-		var options = {};
 
-		options[ scene.id ] = 'Scene';
+	} );
 
-		( function addObjects( objects ) {
+	signals.selected.add( function ( selected ) {
+			
+		var selected = editor.listSelected( 'object' );
+		object = ( selected.length ) ? selected[0] : null;
 
-			for ( var i = 0, l = objects.length; i < l; i ++ ) {
+		updateUI();
 
-				var object = objects[ i ];
+	} );
 
-				options[ object.id ] = object.name;
+	signals.objectChanged.add( function ( changedObject ) {
 
-				addObjects( object.children );
+		if ( object === changedObject ) updateUI();
 
-			}
+	} );
 
-		} )( object.children );
+	function updateUI() {
 
-		objectParent.setOptions( options );
+		if ( !object ) {
 
-	} );
+			container.setDisplay( 'none' );
+			return;
 
-	signals.objectSelected.add( function ( object ) {
+		}
 
-		selected = object;
-		updateUI();
+		container.setDisplay( 'block' );
 
-	} );
-	signals.objectChanged.add( function ( object ) {
+		objectType.setValue( getObjectInstanceName( object ) );
 
-		if ( selected === object ) updateUI();
+		var allObjects = {};
+		var allGeometries = {};
+		var allMaterials = {};
 
-	} );
+		for ( var uuid in editor.objects ) {
 
-	function updateUI() {
+			if ( object.uuid != uuid ) allObjects[ uuid ] = editor.objects[ uuid ].name;
 
-		container.setDisplay( 'block' );
+		}
 
-		var object = selected;
+		for ( var uuid in editor.geometries ) {
 
-		objectType.setValue( getObjectInstanceName( object ) );
+			allGeometries[ uuid ] = editor.geometries[ uuid ].name;
+
+		}
+
+		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 = [];