Browse Source

Merge branch 'ft-kill-l3d' into ft-kill-l3d-oli

Jed 4 năm trước cách đây
mục cha
commit
1612eb9c88

+ 0 - 1
bin/cdb.css

@@ -263,7 +263,6 @@
 }
 }
 .cdb .cdb-sheet td.t_color {
 .cdb .cdb-sheet td.t_color {
   text-align: center;
   text-align: center;
-  position: relative;
 }
 }
 .cdb .cdb-sheet td.t_color ._hide-modal {
 .cdb .cdb-sheet td.t_color ._hide-modal {
   opacity: 0;
   opacity: 0;

+ 0 - 1
bin/cdb.less

@@ -293,7 +293,6 @@
 
 
 		td.t_color {
 		td.t_color {
 			text-align : center;
 			text-align : center;
-			position: relative;
 			._hide-modal {
 			._hide-modal {
 				opacity : 0;
 				opacity : 0;
 			}
 			}

+ 7 - 11
bin/defaultProps.json

@@ -123,18 +123,14 @@
 	"sceneeditor.huds" : {},
 	"sceneeditor.huds" : {},
 
 
 	// l3d config
 	// l3d config
-	"l3d.groundLayer": "ground",
-	"l3d.cdbLevel": "level",
-	"l3d.colors": {
+	"sceneeditor.cdbLevel": "level",
+	"sceneeditor.colors": {
 	},
 	},
-	"l3d.treeStyles": {
-		"settings": {"color": "#ffffff", "font-weight": "bold"}
-	},
-	"l3d.filterTypes": ["terrain", "model", "polygon", "box", "instance", "light"],
-	"l3d.tags": [ { "id": "tag", "color": "#802000" } ],
-	"l3d.camera.moveSpeed": 1.5,
-	"l3d.gridSize": 100,
-	"l3d.gridStep": 1,
+	"sceneeditor.filterTypes": ["terrain", "model", "polygon", "box", "instance", "light"],
+	"sceneeditor.tags": [ { "id": "tag", "color": "#802000" } ],
+	"sceneeditor.camera.moveSpeed": 1.5,
+	"sceneeditor.gridSize": 100,
+	"sceneeditor.gridStep": 1,
 
 
 	// FX editor
 	// FX editor
 	"fx.shaders": [
 	"fx.shaders": [

+ 2 - 0
hide/Config.hx

@@ -12,6 +12,8 @@ typedef HideGlobalConfig = {
 	var recentProjects : Array<String>;
 	var recentProjects : Array<String>;
 
 
 	var windowPos : { x : Int, y : Int, w : Int, h : Int, max : Bool };
 	var windowPos : { x : Int, y : Int, w : Int, h : Int, max : Bool };
+
+	@:optional var sceneEditorLayout : { colsVisible : Bool, colsCombined : Bool };
 }
 }
 
 
 typedef HideProjectConfig = {
 typedef HideProjectConfig = {

+ 1 - 1
hide/Ide.hx

@@ -35,7 +35,7 @@ class Ide {
 		user : Config,
 		user : Config,
 		current : Config,
 		current : Config,
 	};
 	};
-	var ideConfig(get, never) : hide.Config.HideGlobalConfig;
+	public var ideConfig(get, never) : hide.Config.HideGlobalConfig;
 	public var projectConfig(get, never) : hide.Config.HideProjectConfig;
 	public var projectConfig(get, never) : hide.Config.HideProjectConfig;
 
 
 	var window : nw.Window;
 	var window : nw.Window;

+ 10 - 10
hide/comp/SceneEditor.hx

@@ -544,13 +544,13 @@ class SceneEditor {
 			return true;
 			return true;
 		};
 		};
 		tree.onAllowMove = function(e, to) {
 		tree.onAllowMove = function(e, to) {
-			var allowMove = false;
-			if (to == null && e.getHideProps().allowParent == null) allowMove = true;
-			else if (to == null) allowMove = false;
-			else if (to.getHideProps().allowChildren != null && to.getHideProps().allowChildren(e.type) 
-			&& e.getHideProps().allowParent != null && e.getHideProps().allowParent(to))
-				allowMove = true;
-			return allowMove;
+			// var allowMove = false;
+			// if (to == null && e.getHideProps().allowParent == null) allowMove = true;
+			// else if (to == null) allowMove = false;
+			// else if (to.getHideProps().allowChildren != null && to.getHideProps().allowChildren(e.type) 
+			// && e.getHideProps().allowParent != null && e.getHideProps().allowParent(to))
+			// 	allowMove = true;
+			return true;
 		};
 		};
 
 
 		// Batch tree.onMove, which is called for every node moved, causing problems with undo and refresh
 		// Batch tree.onMove, which is called for every node moved, causing problems with undo and refresh
@@ -759,7 +759,7 @@ class SceneEditor {
 			}
 			}
 			// ensure we get onMove even if outside our interactive, allow fast click'n'drag
 			// ensure we get onMove even if outside our interactive, allow fast click'n'drag
 			if( e.button == K.MOUSE_LEFT ) {
 			if( e.button == K.MOUSE_LEFT ) {
-				scene.sevents.startDrag(int.handleEvent);
+				scene.sevents.startCapture(int.handleEvent);
 				e.propagate = false;
 				e.propagate = false;
 			}
 			}
 		};
 		};
@@ -769,7 +769,7 @@ class SceneEditor {
 			curDrag = null;
 			curDrag = null;
 			dragBtn = -1;
 			dragBtn = -1;
 			if( e.button == K.MOUSE_LEFT ) {
 			if( e.button == K.MOUSE_LEFT ) {
-				scene.sevents.stopDrag();
+				scene.sevents.stopCapture();
 				e.propagate = false;
 				e.propagate = false;
 
 
 				var curTime = haxe.Timer.stamp();
 				var curTime = haxe.Timer.stamp();
@@ -1444,7 +1444,7 @@ class SceneEditor {
 			editor.fileView = view;
 			editor.fileView = view;
 			editor.onChange = function(pname) {
 			editor.onChange = function(pname) {
 				edit.onChange(e, 'props.$pname');
 				edit.onChange(e, 'props.$pname');
-				var e = Std.instance(e, Object3D);
+				var e = Std.downcast(e, Object3D);
 				if( e != null ) {
 				if( e != null ) {
 					for( ctx in context.shared.getContexts(e) )
 					for( ctx in context.shared.getContexts(e) )
 						e.addEditorUI(ctx);
 						e.addEditorUI(ctx);

+ 6 - 2
hide/comp/ScriptEditor.hx

@@ -58,8 +58,12 @@ class ScriptChecker {
 			var config = config.get("script.api");
 			var config = config.get("script.api");
 			if( config == null ) continue;
 			if( config == null ) continue;
 			var api = (config : GlobalsDef).get(path);
 			var api = (config : GlobalsDef).get(path);
-			if( api == null ) continue;
-			apis.unshift(api);
+			if( api == null ) {
+				path = ~/\[group=[^\]]+?\]/g.replace(path,"");
+				api = (config : GlobalsDef).get(path);
+			}
+			if( api != null )
+				apis.unshift(api);
 		}
 		}
 
 
 		var cdbPack : String = config.get("script.cdbPackage");
 		var cdbPack : String = config.get("script.cdbPackage");

+ 20 - 19
hide/comp/Toolbar.hx

@@ -1,4 +1,5 @@
 package hide.comp;
 package hide.comp;
+
 enum ToolType {
 enum ToolType {
 	Button;
 	Button;
 	Toggle;
 	Toggle;
@@ -6,17 +7,18 @@ enum ToolType {
 	Color;
 	Color;
 	Menu;
 	Menu;
 }
 }
+
 class ToolsObject {
 class ToolsObject {
-	static public var level3D : hide.view.l3d.Level3D;
+	static public var prefabView : hide.view.Prefab;
 	static var texContent : Element = null;
 	static var texContent : Element = null;
 	static public var tools : Map<String, {title : String, ?icon : String, type : ToolType, ?iconTransform : String, ?rightClick : Void -> Void, ?buttonFunction : Void -> Void, ?toggleFunction : Bool -> Void, ?rangeFunction : Float -> Void, ?colorFunction : Int -> Void, ?menuItems : () -> Array<hide.comp.ContextMenu.ContextMenuItem>}> = [
 	static public var tools : Map<String, {title : String, ?icon : String, type : ToolType, ?iconTransform : String, ?rightClick : Void -> Void, ?buttonFunction : Void -> Void, ?toggleFunction : Bool -> Void, ?rangeFunction : Float -> Void, ?colorFunction : Int -> Void, ?menuItems : () -> Array<hide.comp.ContextMenu.ContextMenuItem>}> = [
-		"perspectiveCamera" => {title : "Perspective camera", icon : "video-camera", type : Button, buttonFunction : () -> @:privateAccess level3D.resetCamera(false)},
-		"topCamera" => {title : "Top camera", icon : "video-camera", type : Button, iconTransform : "rotateZ(90deg)", buttonFunction : () -> @:privateAccess level3D.resetCamera(true)},
-		"snapToGroundToggle" => {title : "Snap to ground", icon : "anchor", type : Toggle, toggleFunction : (v) -> level3D.sceneEditor.snapToGround = v},
-		"localTransformsToggle"=> {title : "Local transforms", icon : "compass", type : Toggle, toggleFunction : (v) -> level3D.sceneEditor.localTransform = v},
-		"gridToggle" => {title : "Toggle grid", icon : "th", type : Toggle, toggleFunction : (v) -> { @:privateAccess level3D.showGrid = v; @:privateAccess level3D.updateGrid(); }},
-		"bakeLights" => {title : "Bake lights", icon : "lightbulb-o", type : Button, buttonFunction : () -> @:privateAccess level3D.bakeLights()},
-		"sceneInformationToggle" => {title : "Scene information", icon : "info-circle", type : Toggle, toggleFunction : (b) -> @:privateAccess level3D.statusText.visible = b, rightClick : () -> {
+		"perspectiveCamera" => {title : "Perspective camera", icon : "video-camera", type : Button, buttonFunction : () -> @:privateAccess prefabView.resetCamera(false)},
+		"topCamera" => {title : "Top camera", icon : "video-camera", type : Button, iconTransform : "rotateZ(90deg)", buttonFunction : () -> @:privateAccess prefabView.resetCamera(true)},
+		"snapToGroundToggle" => {title : "Snap to ground", icon : "anchor", type : Toggle, toggleFunction : (v) -> prefabView.sceneEditor.snapToGround = v},
+		"localTransformsToggle"=> {title : "Local transforms", icon : "compass", type : Toggle, toggleFunction : (v) -> prefabView.sceneEditor.localTransform = v},
+		"gridToggle" => {title : "Toggle grid", icon : "th", type : Toggle, toggleFunction : (v) -> { @:privateAccess prefabView.showGrid = v; @:privateAccess prefabView.updateGrid(); }},
+		"bakeLights" => {title : "Bake lights", icon : "lightbulb-o", type : Button, buttonFunction : () -> @:privateAccess prefabView.bakeLights()},
+		"sceneeditor.sceneInformationToggle" => {title : "Scene information", icon : "info-circle", type : Toggle, toggleFunction : (b) -> @:privateAccess prefabView.statusText.visible = b, rightClick : () -> {
 			if( texContent != null ) {
 			if( texContent != null ) {
 				texContent.remove();
 				texContent.remove();
 				texContent = null;
 				texContent = null;
@@ -25,15 +27,15 @@ class ToolsObject {
 				{
 				{
 					label : "Show Texture Details",
 					label : "Show Texture Details",
 					click : function() {
 					click : function() {
-						var memStats = @:privateAccess level3D.scene.engine.mem.stats();
-						var texs = @:privateAccess level3D.scene.engine.mem.textures;
+						var memStats = @:privateAccess prefabView.scene.engine.mem.stats();
+						var texs = @:privateAccess prefabView.scene.engine.mem.textures;
 						var list = [for(t in texs) {
 						var list = [for(t in texs) {
 							n: '${t.width}x${t.height}  ${t.format}  ${t.name}',
 							n: '${t.width}x${t.height}  ${t.format}  ${t.name}',
 							size: t.width * t.height
 							size: t.width * t.height
 						}];
 						}];
 						list.sort((a, b) -> Reflect.compare(b.size, a.size));
 						list.sort((a, b) -> Reflect.compare(b.size, a.size));
 						var content = new Element('<div tabindex="1" class="overlay-info"><h2>Scene info</h2><pre></pre></div>');
 						var content = new Element('<div tabindex="1" class="overlay-info"><h2>Scene info</h2><pre></pre></div>');
-						new Element(@:privateAccess level3D.element[0].ownerDocument.body).append(content);
+						new Element(@:privateAccess prefabView.element[0].ownerDocument.body).append(content);
 						var pre = content.find("pre");
 						var pre = content.find("pre");
 						pre.text([for(l in list) l.n].join("\n"));
 						pre.text([for(l in list) l.n].join("\n"));
 						texContent = content;
 						texContent = content;
@@ -45,14 +47,13 @@ class ToolsObject {
 				}
 				}
 			]);
 			]);
 		}},
 		}},
-		"autoSyncToggle" => {title : "Auto synchronize", icon : "refresh", type : Toggle, toggleFunction : (b) -> @:privateAccess level3D.autoSync = b},
-		"graphicsFilters" => {title : "Graphics filters", type : Menu, menuItems : () -> @:privateAccess level3D.filtersToMenuItem(level3D.graphicsFilters, "Graphics")},
-		"sceneFilters" => {title : "Scene filters", type : Menu, menuItems : () -> @:privateAccess level3D.filtersToMenuItem(level3D.sceneFilters, "Scene")},
-		"backgroundColor" => {title : "Background Color", type : Color, colorFunction :  function(v) {
-			@:privateAccess level3D.scene.engine.backgroundColor = v;
-			@:privateAccess level3D.updateGrid();}},
-		"sceneSpeed" => {title : "Speed", type : Range, rangeFunction : function(v) @:privateAccess level3D.scene.speed = v},
-		
+		"sceneeditor.autoSyncToggle" => {title : "Auto synchronize", icon : "refresh", type : Toggle, toggleFunction : (b) -> @:privateAccess prefabView.autoSync = b},
+		"graphicsFilters" => {title : "Graphics filters", type : Menu, menuItems : () -> @:privateAccess prefabView.filtersToMenuItem(prefabView.graphicsFilters, "Graphics")},
+		"sceneFilters" => {title : "Scene filters", type : Menu, menuItems : () -> @:privateAccess prefabView.filtersToMenuItem(prefabView.sceneFilters, "Scene")},
+		"sceneeditor.backgroundColor" => {title : "Background Color", type : Color, colorFunction :  function(v) {
+			@:privateAccess prefabView.scene.engine.backgroundColor = v;
+			@:privateAccess prefabView.updateGrid();}},
+		"sceneeditor.sceneSpeed" => {title : "Speed", type : Range, rangeFunction : function(v) @:privateAccess prefabView.scene.speed = v}
 	];
 	];
 }
 }
 typedef ToolToggle = {
 typedef ToolToggle = {

+ 5 - 2
hide/comp/cdb/Cell.hx

@@ -409,8 +409,11 @@ class Cell extends Component {
 		// keywords
 		// keywords
 		code = KWD_REG.map(code, function(r) return '<span class="kwd">${r.matched(0)}</span>');
 		code = KWD_REG.map(code, function(r) return '<span class="kwd">${r.matched(0)}</span>');
 		// comments
 		// comments
-		code = ~/(\/\*([^\*]+)\*\/)/g.replace(code,'<span class="comment">$1</span>');
-		code = code.split("<br/>").map(function(line) return ~/(\/\/.*)/.replace(line,'<span class="comment">$1</span>')).join("<br/>");
+		function unspan(str:String) {
+			return str.split('<span class="').join('<span class="_');
+		}
+		code = ~/(\/\*([^\*]+)\*\/)/g.map(code,function(r) return '<span class="comment">'+unspan(r.matched(1))+'</span>');
+		code = code.split("<br/>").map(function(line) return ~/(\/\/.*)/.map(line,(r) -> '<span class="comment">'+unspan(r.matched(1))+'</span>')).join("<br/>");
 		return code;
 		return code;
 	}
 	}
 
 

+ 9 - 4
hide/comp/cdb/DataFiles.hx

@@ -137,7 +137,7 @@ class DataFiles {
 			gatherRec(dir.split("/"),[],0);
 			gatherRec(dir.split("/"),[],0);
 	}
 	}
 
 
-	public static function save( ?onSaveBase, ?force ) {
+	public static function save( ?onSaveBase, ?force, ?prevSheetNames : Map<String,String> ) {
 		var ide = Ide.inst;
 		var ide = Ide.inst;
 		var temp = [];
 		var temp = [];
 		var titles = [];
 		var titles = [];
@@ -160,6 +160,9 @@ class DataFiles {
 			if( s.props.dataFiles != null ) {
 			if( s.props.dataFiles != null ) {
 				var sheet = @:privateAccess s.sheet;
 				var sheet = @:privateAccess s.sheet;
 				var sheetName = getTypeName(s);
 				var sheetName = getTypeName(s);
+				var prevName = sheetName;
+				if( prevSheetNames != null && prevSheetNames.exists(sheetName) )
+					prevName = prevSheetNames.get(sheetName);
 				var ldata = sheet.linesData;
 				var ldata = sheet.linesData;
 				for( i in 0...s.lines.length ) {
 				for( i in 0...s.lines.length ) {
 					var o = s.lines[i];
 					var o = s.lines[i];
@@ -174,10 +177,12 @@ class DataFiles {
 						}
 						}
 						var all = pf.getPrefabsByPath(p.path);
 						var all = pf.getPrefabsByPath(p.path);
 						var inst : hrt.prefab.Prefab = all[p.index];
 						var inst : hrt.prefab.Prefab = all[p.index];
-						if( inst == null || inst.getCdbType() != sheetName )
+						if( inst == null || inst.getCdbType() != prevName )
 							ide.error("Can't save prefab data "+p.path);
 							ide.error("Can't save prefab data "+p.path);
-						else
+						else {
+							if( prevName != sheetName ) Reflect.setField(o,"$cdbtype", sheetName);
 							inst.props = o;
 							inst.props = o;
+						}
 					}
 					}
 				}
 				}
 				var old = Reflect.copy(sheet);
 				var old = Reflect.copy(sheet);
@@ -214,7 +219,7 @@ class DataFiles {
 	public static function getAvailableTypes() {
 	public static function getAvailableTypes() {
 		var sheets = [];
 		var sheets = [];
 		var ide = Ide.inst;
 		var ide = Ide.inst;
-		var levelSheet = ide.database.getSheet(ide.currentConfig.get("l3d.cdbLevel", "level"));
+		var levelSheet = ide.database.getSheet(ide.currentConfig.get("sceneeditor.cdbLevel", "level"));
 		for( s in ide.database.sheets )
 		for( s in ide.database.sheets )
 			if( s.props.dataFiles != null )
 			if( s.props.dataFiles != null )
 				sheets.push(s);
 				sheets.push(s);

+ 8 - 7
hide/comp/cdb/Editor.hx

@@ -857,28 +857,28 @@ class Editor extends Component {
 			{ label : "Edit", click : function () editColumn(sheet, col) },
 			{ label : "Edit", click : function () editColumn(sheet, col) },
 			{ label : "Add Column", click : function () newColumn(sheet, indexColumn) },
 			{ label : "Add Column", click : function () newColumn(sheet, indexColumn) },
 			{ label : "", isSeparator: true },
 			{ label : "", isSeparator: true },
-			{ label : "Move Left", enabled:  (indexColumn > 0 && 
+			{ label : "Move Left", enabled:  (indexColumn > 0 &&
 				nextVisibleColumnIndex(table, indexColumn, Left) > -1), click : function () {
 				nextVisibleColumnIndex(table, indexColumn, Left) > -1), click : function () {
 				beginChanges();
 				beginChanges();
 				var nextIndex = nextVisibleColumnIndex(table, indexColumn, Left);
 				var nextIndex = nextVisibleColumnIndex(table, indexColumn, Left);
 				sheet.columns.remove(col);
 				sheet.columns.remove(col);
 				sheet.columns.insert(nextIndex, col);
 				sheet.columns.insert(nextIndex, col);
-				if (cursor.x == indexColumn) 
+				if (cursor.x == indexColumn)
 					cursor.set(cursor.table, nextIndex, cursor.y);
 					cursor.set(cursor.table, nextIndex, cursor.y);
-				else if (cursor.x == nextIndex) 
+				else if (cursor.x == nextIndex)
 					cursor.set(cursor.table, nextIndex + 1, cursor.y);
 					cursor.set(cursor.table, nextIndex + 1, cursor.y);
 				endChanges();
 				endChanges();
 				refresh();
 				refresh();
 			}},
 			}},
-			{ label : "Move Right", enabled: (indexColumn < sheet.columns.length - 1 && 
+			{ label : "Move Right", enabled: (indexColumn < sheet.columns.length - 1 &&
 				nextVisibleColumnIndex(table, indexColumn, Right) < sheet.columns.length), click : function () {
 				nextVisibleColumnIndex(table, indexColumn, Right) < sheet.columns.length), click : function () {
 				beginChanges();
 				beginChanges();
 				var nextIndex = nextVisibleColumnIndex(table, indexColumn, Right);
 				var nextIndex = nextVisibleColumnIndex(table, indexColumn, Right);
 				sheet.columns.remove(col);
 				sheet.columns.remove(col);
 				sheet.columns.insert(nextIndex, col);
 				sheet.columns.insert(nextIndex, col);
-				if (cursor.x == indexColumn) 
+				if (cursor.x == indexColumn)
 					cursor.set(cursor.table, nextIndex, cursor.y);
 					cursor.set(cursor.table, nextIndex, cursor.y);
-				else if (cursor.x == nextIndex) 
+				else if (cursor.x == nextIndex)
 					cursor.set(cursor.table, nextIndex - 1, cursor.y);
 					cursor.set(cursor.table, nextIndex - 1, cursor.y);
 				endChanges();
 				endChanges();
 				refresh();
 				refresh();
@@ -927,7 +927,7 @@ class Editor extends Component {
 			})});
 			})});
 
 
 			switch(col.type) {
 			switch(col.type) {
-			case TId | TString: 
+			case TId | TString:
 				menu.push({ label : "Sort", click: () -> table.sortBy(col) });
 				menu.push({ label : "Sort", click: () -> table.sortBy(col) });
 			default:
 			default:
 			}
 			}
@@ -1089,6 +1089,7 @@ class Editor extends Component {
 			if( StringTools.startsWith(s.name, old + "@") )
 			if( StringTools.startsWith(s.name, old + "@") )
 				s.rename(name + "@" + s.name.substr(old.length + 1));
 				s.rename(name + "@" + s.name.substr(old.length + 1));
 		endChanges();
 		endChanges();
+		DataFiles.save(true,[ sheet.name => old ]);
 		return true;
 		return true;
 	}
 	}
 
 

+ 1 - 1
hide/view/Image.hx

@@ -74,7 +74,7 @@ class Image extends FileView {
 					new hide.view.l3d.CameraController2D(scene.s2d);
 					new hide.view.l3d.CameraController2D(scene.s2d);
 				} else {
 				} else {
 					var r = new h3d.scene.fwd.Renderer();
 					var r = new h3d.scene.fwd.Renderer();
-					scene.s3d.lightSystem.ambientLight.set(1,1,1,1);
+					cast(scene.s3d.lightSystem,h3d.scene.fwd.LightSystem).ambientLight.set(1,1,1,1);
 					scene.s3d.renderer = r;
 					scene.s3d.renderer = r;
 					var sp = new h3d.prim.Sphere(1,64,64);
 					var sp = new h3d.prim.Sphere(1,64,64);
 					sp.addNormals();
 					sp.addNormals();

+ 4 - 4
hide/view/Model.hx

@@ -575,11 +575,11 @@ class Model extends FileView {
 				var prevPause = obj.currentAnimation.pause;
 				var prevPause = obj.currentAnimation.pause;
 				obj.currentAnimation.pause = true;
 				obj.currentAnimation.pause = true;
 				obj.currentAnimation.setFrame( (e.relX / W) * obj.currentAnimation.frameCount );
 				obj.currentAnimation.setFrame( (e.relX / W) * obj.currentAnimation.frameCount );
-				int.startDrag(function(e) {
+				int.startCapture(function(e) {
 					switch(e.kind ) {
 					switch(e.kind ) {
 					case ERelease:
 					case ERelease:
 						obj.currentAnimation.pause = prevPause;
 						obj.currentAnimation.pause = prevPause;
-						int.stopDrag();
+						int.stopCapture();
 					case EMove:
 					case EMove:
 						obj.currentAnimation.setFrame( (e.relX / W) * obj.currentAnimation.frameCount );
 						obj.currentAnimation.setFrame( (e.relX / W) * obj.currentAnimation.frameCount );
 					default:
 					default:
@@ -658,10 +658,10 @@ class Model extends FileView {
 					dragInter.onPush = function(e) {
 					dragInter.onPush = function(e) {
 						if( hxd.Key.isDown( hxd.Key.MOUSE_LEFT) ){
 						if( hxd.Key.isDown( hxd.Key.MOUSE_LEFT) ){
 							var startFrame = curFrame;
 							var startFrame = curFrame;
-							dragInter.startDrag(function(e) {
+							dragInter.startCapture(function(e) {
 								switch( e.kind ) {
 								switch( e.kind ) {
 								case ERelease:
 								case ERelease:
-									dragInter.stopDrag();
+									dragInter.stopCapture();
 									buildTimeline();
 									buildTimeline();
 									buildEventPanel();
 									buildEventPanel();
 									if( curFrame != startFrame )
 									if( curFrame != startFrame )

+ 10 - 8
hide/view/Particles3D.hx

@@ -299,14 +299,16 @@ class Particles3D extends FileView {
 		extra = properties.add(extra, props, function(_) syncProps());
 		extra = properties.add(extra, props, function(_) syncProps());
 
 
 		extra.find(".bounds").change(function(e) bounds.visible = e.getThis().prop("checked"));
 		extra.find(".bounds").change(function(e) bounds.visible = e.getThis().prop("checked"));
-		var defAmbient = scene.s3d.lightSystem.ambientLight.clone();
-		extra.find(".lights").change(function(e) {
-			var ls = scene.s3d.lightSystem;
-			var lfw = Std.downcast(ls, h3d.scene.fwd.LightSystem);
-			var enable = e.getThis().prop("checked");
-			if( lfw != null ) lfw.maxLightsPerObject = enable ? 6 : 0;
-			if( enable ) ls.ambientLight.load(defAmbient) else ls.ambientLight.set(1, 1, 1);
-		});
+		var lfw = Std.downcast(scene.s3d.lightSystem,h3d.scene.fwd.LightSystem);
+		if( lfw != null ) {
+			var defAmbient = lfw.ambientLight.clone();
+			extra.find(".lights").change(function(e) {
+				lfw = Std.downcast(scene.s3d.lightSystem, h3d.scene.fwd.LightSystem);
+				var enable = e.getThis().prop("checked");
+				if( lfw != null ) lfw.maxLightsPerObject = enable ? 6 : 0;
+				if( enable ) lfw.ambientLight.load(defAmbient) else lfw.ambientLight.set(1, 1, 1);
+			});
+		}
 		extra.find(".new").click(function(_) {
 		extra.find(".new").click(function(_) {
 			var g = parts.addGroup();
 			var g = parts.addGroup();
 			g.name = "Group#" + Lambda.count({ iterator : parts.getGroups });
 			g.name = "Group#" + Lambda.count({ iterator : parts.getGroups });

+ 710 - 76
hide/view/Prefab.hx

@@ -1,58 +1,290 @@
 package hide.view;
 package hide.view;
+using Lambda;
+
+import hxd.Math;
+import hxd.Key as K;
+
+import hrt.prefab.Prefab as PrefabElement;
+import hrt.prefab.Object3D;
+import hrt.prefab.l3d.Instance;
+import hide.comp.cdb.DataFiles;
 
 
-import hrt.prefab.Prefab in PrefabElement;
+
+@:access(hide.view.Prefab)
+class CamController extends h3d.scene.CameraController {
+	public var groundSnapAngle = hxd.Math.degToRad(30);
+	var prefab : Prefab;
+	var startPush : h2d.col.Point;
+	var moveCount = 0;
+
+	public function new(parent, prefab) {
+		super(null, parent);
+		this.prefab = prefab;
+	}
+
+	override function onEvent( e : hxd.Event ) {
+		if(curPos == null) return;
+		switch( e.kind ) {
+		case EWheel:
+			zoom(e.wheelDelta);
+		case EPush:
+			pushing = e.button;
+			pushTime = haxe.Timer.stamp();
+			pushStartX = pushX = e.relX;
+			pushStartY = pushY = e.relY;
+			startPush = new h2d.col.Point(pushX, pushY);
+			if( pushing == 2 ) {
+				var se = prefab.sceneEditor;
+				var selection = se.getSelection();
+				var angle = hxd.Math.abs(Math.PI/2 - phi);
+				if( selection.length == 0 && angle > groundSnapAngle ) {
+					var visGround = se.screenToGround(se.scene.s2d.width / 2, se.scene.s2d.height / 2);
+					var dist = se.screenDistToGround(se.scene.s2d.width / 2, se.scene.s2d.height / 2);
+					if( dist != null ) {
+						set(dist, null, null, visGround);
+					}
+				}
+			}
+			moveCount = 0;
+			@:privateAccess scene.window.mouseLock = true;
+		case ERelease, EReleaseOutside:
+			if( pushing == e.button ) {
+				pushing = -1;
+				startPush = null;
+				if( e.kind == ERelease && haxe.Timer.stamp() - pushTime < 0.2 && hxd.Math.distance(e.relX - pushStartX,e.relY - pushStartY) < 5 )
+					onClick(e);
+				@:privateAccess scene.window.mouseLock = false;
+			}
+		case EMove:
+			// Windows bug that jumps movementX/Y on all browsers
+			if( moveCount < 10 && Math.distanceSq(pushX - e.relX, pushY - e.relY) > 100000 ) {
+				pushX = e.relX;
+				pushY = e.relY;
+				return;
+			}
+			moveCount++;
+
+			switch( pushing ) {
+			case 1:
+				if(startPush != null && startPush.distance(new h2d.col.Point(e.relX, e.relY)) > 3) {
+					var angle = hxd.Math.abs(Math.PI/2 - phi);
+					if(K.isDown(K.SHIFT) || angle < groundSnapAngle) {
+						var m = 0.001 * curPos.x * panSpeed / 25;
+						pan(-(e.relX - pushX) * m, (e.relY - pushY) * m);
+					}
+					else {
+						var se = prefab.sceneEditor;
+						var fromPt = se.screenToGround(pushX, pushY);
+						var toPt = se.screenToGround(e.relX, e.relY);
+						if(fromPt == null || toPt == null)
+							return;
+						var delta = toPt.sub(fromPt).toVector();
+						delta.w = 0;
+						targetOffset = targetOffset.sub(delta);
+					}
+				}
+				pushX = e.relX;
+				pushY = e.relY;
+			case 2:
+				rot(e.relX - pushX, e.relY - pushY);
+				pushX = e.relX;
+				pushY = e.relY;
+			default:
+			}
+		case EFocus:
+			@:privateAccess scene.window.mouseLock = false;
+		default:
+		}
+	}
+
+	function moveKeys() {
+		var mov = new h3d.Vector();
+		if( K.isDown(K.UP) || K.isDown(K.Z) || K.isDown(K.W) )
+			mov.x += 1;
+		if( K.isDown(K.DOWN) || K.isDown(K.S) )
+			mov.x -= 1;
+		if( K.isDown(K.LEFT) || K.isDown(K.Q) || K.isDown(K.A) )
+			mov.y -= 1;
+		if( K.isDown(K.RIGHT) || K.isDown(K.D) )
+			mov.y += 1;
+
+		if( mov.x == 0 && mov.y == 0 )
+			return;
+		var dir = new h3d.Vector(
+			mov.x * Math.cos(theta) + mov.y * Math.cos(Math.PI / 2 + theta),
+			mov.x * Math.sin(theta) + mov.y * Math.sin(Math.PI / 2 + theta)
+		);
+		var moveSpeed = Ide.inst.currentConfig.get("sceneeditor.camera.moveSpeed", 1.5);
+
+		var delta = dir.multiply(0.01 * moveSpeed * (distance + scene.camera.zNear));
+		delta.w = 0;
+		targetOffset = targetOffset.sub(delta);
+	}
+
+	override function sync(ctx : h3d.scene.RenderContext) {
+		if( pushing == 2 ) {
+			moveKeys();
+		}
+		super.sync(ctx);
+	}
+}
 
 
 @:access(hide.view.Prefab)
 @:access(hide.view.Prefab)
 private class PrefabSceneEditor extends hide.comp.SceneEditor {
 private class PrefabSceneEditor extends hide.comp.SceneEditor {
 	var parent : Prefab;
 	var parent : Prefab;
+
 	public function new(view, data) {
 	public function new(view, data) {
 		super(view, data);
 		super(view, data);
 		parent = cast view;
 		parent = cast view;
+		this.localTransform = false; // TODO: Expose option
 	}
 	}
-	override function onSceneReady() {
-		super.onSceneReady();
-		parent.onSceneReady();
+
+	override function makeCamController() {
+		var c = new CamController(scene.s3d, parent);
+		c.friction = 0.9;
+		c.panSpeed = 0.6;
+		c.zoomAmount = 1.05;
+		c.smooth = 0.7;
+		c.minDistance = 1;
+		return c;
+	}
+
+	override function refresh(?mode, ?callback) {
+		parent.onRefresh();
+		super.refresh(mode, callback);
 	}
 	}
+
 	override function update(dt) {
 	override function update(dt) {
 		super.update(dt);
 		super.update(dt);
 		parent.onUpdate(dt);
 		parent.onUpdate(dt);
 	}
 	}
-	override function refresh(?mode, ?callb:Void->Void) {
-		refreshScene();
-		refreshTree(callb);
+
+	override function onSceneReady() {
+		super.onSceneReady();
+		parent.onSceneReady();
+	}
+
+	override function applyTreeStyle(p: PrefabElement, el: Element, ?pname: String) {
+		super.applyTreeStyle(p, el, pname);
+		parent.applyTreeStyle(p, el, pname);
+	}
+
+	override function applySceneStyle(p:PrefabElement) {
+		parent.applySceneStyle(p);
+	}
+
+	override function onPrefabChange(p: PrefabElement, ?pname: String) {
+		super.onPrefabChange(p, pname);
+		parent.onPrefabChange(p, pname);
+	}
+
+	override function getNewContextMenu(current: PrefabElement, ?onMake: PrefabElement->Void=null, ?groupByType = true ) {
+		var newItems = super.getNewContextMenu(current, onMake, groupByType);
+
+		function setup(p : PrefabElement) {
+			autoName(p);
+			haxe.Timer.delay(addElements.bind([p]), 0);
+		}
+
+		function addNewInstances() {
+			var items = new Array<hide.comp.ContextMenu.ContextMenuItem>();
+			for(type in DataFiles.getAvailableTypes() ) {
+				var typeId = DataFiles.getTypeName(type);
+				var label = typeId.charAt(0).toUpperCase() + typeId.substr(1);
+
+				var refCols = Instance.findRefColumns(type);
+				var refSheet = refCols == null ? null : type.base.getSheet(refCols.sheet);
+				var idCol = refCols == null ? null : Instance.findIDColumn(refSheet);
+
+				function make(name) {
+					var p = new Instance(current == null ? sceneData : current);
+					p.name = name;
+					p.props = makeCdbProps(p, type);
+					setup(p);
+					if(onMake != null)
+						onMake(p);
+					return p;
+				}
+
+				if(idCol != null && refSheet.props.dataFiles == null ) {
+					var kindItems = new Array<hide.comp.ContextMenu.ContextMenuItem>();
+					for(line in refSheet.lines) {
+						var kind : String = Reflect.getProperty(line, idCol.name);
+						kindItems.push({
+							label : kind,
+							click : function() {
+								var p = make(kind.charAt(0).toLowerCase() + kind.substr(1));
+								var obj : Dynamic = p.props;
+								for( c in refCols.cols ) {
+									if( c == refCols.cols[refCols.cols.length-1] )
+										Reflect.setField(obj, c.name, kind);
+									else {
+										var s = Reflect.field(obj,c.name);
+										if( s == null ) {
+											s = {};
+											Reflect.setField(obj, c.name, s);
+										}
+										obj = s;
+									}
+								}
+							}
+						});
+					}
+					items.unshift({
+						label : label,
+						menu: kindItems
+					});
+				}
+				else {
+					items.push({
+						label : label,
+						click : make.bind(typeId)
+					});
+				}
+			}
+			newItems.unshift({
+				label : "Instance",
+				menu: items
+			});
+		};
+		addNewInstances();
+		return newItems;
+	}
+
+	override function getAvailableTags(p:PrefabElement) {
+		return cast ide.currentConfig.get("sceneeditor.tags");
 	}
 	}
 }
 }
 
 
 class Prefab extends FileView {
 class Prefab extends FileView {
 
 
-	var sceneEditor : PrefabSceneEditor;
+	public var sceneEditor : PrefabSceneEditor;
 	var data : hrt.prefab.Library;
 	var data : hrt.prefab.Library;
-	var tabs : hide.comp.Tabs;
 
 
 	var tools : hide.comp.Toolbar;
 	var tools : hide.comp.Toolbar;
-	var light : h3d.scene.fwd.DirLight;
-	var lightDirection = new h3d.Vector( 1, 2, -4 );
 
 
-	var scene(get, null):  hide.comp.Scene;
-	function get_scene() return sceneEditor.scene;
-	var properties(get, null):  hide.comp.PropsEditor;
-	function get_properties() return sceneEditor.properties;
+	var layerToolbar : hide.comp.Toolbar;
+	var layerButtons : Map<PrefabElement, hide.comp.Toolbar.ToolToggle>;
+
+	var grid : h3d.scene.Graphics;
+
+	var gridStep : Int;
+	var gridSize : Int;
+	var showGrid = false;
 
 
 	// autoSync
 	// autoSync
 	var autoSync : Bool;
 	var autoSync : Bool;
 	var currentVersion : Int = 0;
 	var currentVersion : Int = 0;
 	var lastSyncChange : Float = 0.;
 	var lastSyncChange : Float = 0.;
+	var sceneFilters : Map<String, Bool>;
+	var graphicsFilters : Map<String, Bool>;
+	var statusText : h2d.Text;
+	var posToolTip : h2d.Text;
 
 
-	override function getDefaultContent() {
-		return haxe.io.Bytes.ofString(ide.toJSON(new hrt.prefab.Library().saveData()));
-	}
-
-	override function save() {
-		var content = ide.toJSON(data.saveData());
-		currentSign = haxe.crypto.Md5.encode(content);
-		sys.io.File.saveContent(getPath(), content);
-		super.save();
-	}
+	var scene(get, null):  hide.comp.Scene;
+	function get_scene() return sceneEditor.scene;
+	public var properties(get, null):  hide.comp.PropsEditor;
+	function get_properties() return sceneEditor.properties;
 
 
 	override function onDisplay() {
 	override function onDisplay() {
 		if( sceneEditor != null ) sceneEditor.dispose();
 		if( sceneEditor != null ) sceneEditor.dispose();
@@ -63,73 +295,335 @@ class Prefab extends FileView {
 		currentSign = haxe.crypto.Md5.encode(content);
 		currentSign = haxe.crypto.Md5.encode(content);
 
 
 		element.html('
 		element.html('
-			<div class="flex vertical prefabview">
-				<div class="toolbar"></div>
-				<div class="flex-elt">
-					<div class="heaps-scene">
-					</div>
-					<div class="tabs">
-						<div class="tab expand" name="Scene" icon="sitemap">
-							<div class="hide-block">
-								<div class="hide-list scenetree">
+			<div class="flex vertical">
+				<div style="flex: 0 0 30px;">
+					<span class="tools-buttons"></span>
+					<span class="layer-buttons"></span>
+				</div>
+
+				<div class="scene-partition" style="display: flex; flex-direction: row; flex: 1; overflow: hidden;">
+					<div class="heaps-scene"></div>
+
+					<div class="tree-column">
+						<div class="flex vertical">
+							<div class="hide-toolbar">
+								<div class="toolbar-label">
+									<div class="icon fa fa-sitemap"></div>
+									Scene
+								</div>
+								<div class="button collapse-btn" title="Collapse all">
+									<div class="icon fa fa-reply-all"></div>
+								</div>
+
+								<div class="button combine-btn layout-btn" title="Move to bottom of tree">
+									<div class="icon fa fa-compress"></div>
+								</div>
+								<div class="button separate-btn layout-btn" title="Move to own column">
+									<div class="icon fa fa-expand"></div>
+								</div>
+
+								<div class="button hide-cols-btn close-btn" title="Hide Tree & Props">
+									<div class="icon fa fa-chevron-right"></div>
 								</div>
 								</div>
 							</div>
 							</div>
+
+							<div class="hide-scenetree"></div>
 						</div>
 						</div>
 					</div>
 					</div>
+
+					<div class="props-column">
+						<div class="hide-toolbar">
+							<div class="toolbar-label">
+								<div class="icon fa fa-sitemap"></div>
+								Properties
+							</div>
+						</div>
+							<div class="hide-scroll"></div>
+					</div>
+
+					<div class="button show-cols-btn close-btn" title="Show Tree & Props">
+						<div class="icon fa fa-chevron-left"></div>
+					</div>
 				</div>
 				</div>
 			</div>
 			</div>
 		');
 		');
-		tools = new hide.comp.Toolbar(null,element.find(".toolbar"));
-		tabs = new hide.comp.Tabs(null,element.find(".tabs"));
-		sceneEditor = new PrefabSceneEditor(this, data);
-		element.find(".scenetree").first().append(sceneEditor.tree.element);
-		element.find(".tab").first().append(sceneEditor.properties.element);
-		element.find(".heaps-scene").first().append(sceneEditor.scene.element);
+
+		tools = new hide.comp.Toolbar(null,element.find(".tools-buttons"));
+		layerToolbar = new hide.comp.Toolbar(null,element.find(".layer-buttons"));
 		currentVersion = undo.currentID;
 		currentVersion = undo.currentID;
+
+		sceneEditor = new PrefabSceneEditor(this, data);
+		element.find(".hide-scenetree").first().append(sceneEditor.tree.element);
+		element.find(".hide-scroll").first().append(properties.element);
+		element.find(".heaps-scene").first().append(scene.element);
+		sceneEditor.tree.element.addClass("small");
+
+		refreshColLayout();
+		element.find(".combine-btn").first().click((_) -> setCombine(true));
+		element.find(".separate-btn").first().click((_) -> setCombine(false));
+
+		element.find(".show-cols-btn").first().click(showColumns);
+		element.find(".hide-cols-btn").first().click(hideColumns);
+
+		element.find(".collapse-btn").click(function(e) {
+			sceneEditor.collapseTree();
+		});
+
+		refreshSceneFilters();
+		refreshGraphicsFilters();
 	}
 	}
 
 
-	public function onSceneReady() {
-		tabs.allowMask(scene);
+	function refreshColLayout() {
+		var config = ide.ideConfig;
+		if( config.sceneEditorLayout == null ) {
+			config.sceneEditorLayout = {
+				colsVisible: true,
+				colsCombined: false,
+			};
+		}
+		setCombine(config.sceneEditorLayout.colsCombined);
 
 
-		// TOMORROW this isn't in the Level3D view
-		{
-			light = sceneEditor.scene.s3d.find(function(o) return Std.downcast(o, h3d.scene.fwd.DirLight));
-			if( light == null ) {
-				light = new h3d.scene.fwd.DirLight(scene.s3d);
-				light.enableSpecular = true;
-			} else
-				light = null;
+		if( config.sceneEditorLayout.colsVisible )
+			showColumns();
+		else
+			hideColumns();
+	}
+
+	override function onActivate() {
+		if( sceneEditor != null )
+			refreshColLayout();
+	}
+
+	function hideColumns(?_) {
+		element.find(".tree-column").first().hide();
+		element.find(".props-column").first().hide();
+		element.find(".show-cols-btn").first().show();
+		ide.ideConfig.sceneEditorLayout.colsVisible = false;
+		@:privateAccess ide.config.global.save();
+		@:privateAccess if( scene.window != null) scene.window.checkResize();
+	}
+
+	function showColumns(?_) {
+		element.find(".tree-column").first().show();
+		element.find(".props-column").first().show();
+		element.find(".show-cols-btn").first().hide();
+		ide.ideConfig.sceneEditorLayout.colsVisible = true;
+		@:privateAccess ide.config.global.save();
+		@:privateAccess if( scene.window != null) scene.window.checkResize();
+	}
+
+	function setCombine(val) {
+		var fullscene = element.find(".scene-partition").first();
+		var props = element.find(".props-column").first();
+		fullscene.toggleClass("reduced-columns", val);
+		if( val ) {
+			element.find(".hide-scenetree").first().parent().append(props);
+			element.find(".combine-btn").first().hide();
+			element.find(".separate-btn").first().show();
+		} else {
+			fullscene.append(props);
+			element.find(".combine-btn").first().show();
+			element.find(".separate-btn").first().hide();
 		}
 		}
+		ide.ideConfig.sceneEditorLayout.colsCombined = val;
+		@:privateAccess ide.config.global.save();
+		@:privateAccess if( scene.window != null) scene.window.checkResize();
+	}
 
 
-		tools.saveDisplayKey = "Prefab/tools";
+	public function onSceneReady() {
+		tools.saveDisplayKey = "Prefab/toolbar";
+		statusText = new h2d.Text(hxd.res.DefaultFont.get(), scene.s2d);
+		statusText.setPosition(5, 5);
+		statusText.visible = false;
+		hide.comp.Toolbar.ToolsObject.prefabView = this;
+		var toolsNames : Iterator<String> = hide.comp.Toolbar.ToolsObject.tools.keys();
+		for (toolName in toolsNames) {
+			var tool = hide.comp.Toolbar.ToolsObject.tools.get(toolName);
+			if (tool != null) {
+				var shortcut = (config.get("key.sceneeditor." + toolName) != null)? " (" + config.get("key.sceneeditor." + toolName) + ")" : "";
+				switch(tool.type) {
+					case Button:
+						var button = tools.addButton(tool.icon, tool.title + shortcut, tool.buttonFunction);
+						if (tool.iconTransform != null) {
+							button.find(".icon").css({transform: tool.iconTransform});
+						}
+					case Toggle:
+						var toggle = tools.addToggle(tool.icon, tool.title + shortcut, tool.toggleFunction);
+						if (tool.iconTransform != null) {
+							toggle.element.find(".icon").css({transform: tool.iconTransform});
+						}
+						keys.register("sceneeditor." + toolName, () -> toggle.toggle(!toggle.isDown()));
+						if (tool.rightClick != null) {
+							toggle.rightClick(tool.rightClick);
+						}
+					case Color:
+						tools.addColor(tool.title, tool.colorFunction);
+					case Range:
+						tools.addRange(tool.title, tool.rangeFunction, 1.);
 
 
-		tools.addToggle("arrows", "2D Camera", (b) -> sceneEditor.camera2D = b);
-		tools.addButton("video-camera", "Default camera", () -> sceneEditor.resetCamera());
+				}
+			}
+		}
+		posToolTip = new h2d.Text(hxd.res.DefaultFont.get(), scene.s2d);
+		posToolTip.dropShadow = { dx : 1, dy : 1, color : 0, alpha : 0.5 };
 
 
-		tools.addColor("Background color", function(v) {
-			scene.engine.backgroundColor = v;
-		}, scene.engine.backgroundColor);
-		tools.addToggle("refresh", "Auto synchronize", function(b) {
-			autoSync = b;
-		});
-		tools.addRange("Speed", function(v) {
-			scene.speed = v;
-		}, scene.speed);
+		updateStats();
+		updateGrid();
+		initGraphicsFilters();
 	}
 	}
 
 
-	function onUpdate(dt:Float) {
-		var cam = scene.s3d.camera;
+	function updateStats() {
+		var memStats = scene.engine.mem.stats();
+		@:privateAccess
+		var lines : Array<String> = [
+			'Scene objects: ${scene.s3d.getObjectsCount()}',
+			'Interactives: ' + sceneEditor.interactives.count(),
+			'Contexts: ' + sceneEditor.context.shared.contexts.count(),
+			'Triangles: ${scene.engine.drawTriangles}',
+			'Buffers: ${memStats.bufferCount}',
+			'Textures: ${memStats.textureCount}',
+			'FPS: ${Math.round(scene.engine.realFps)}',
+			'Draw Calls: ${scene.engine.drawCalls}',
+		];
+		statusText.text = lines.join("\n");
+		haxe.Timer.delay(function() sceneEditor.event.wait(0.5, updateStats), 0);
+	}
 
 
-		// TOMORROW this isn't in the Level3D view
-		{
-			if( light != null ) {
-				var angle = Math.atan2(cam.target.y - cam.pos.y, cam.target.x - cam.pos.x);
-				light.setDirection(new h3d.Vector(
-					Math.cos(angle) * lightDirection.x - Math.sin(angle) * lightDirection.y,
-					Math.sin(angle) * lightDirection.x + Math.cos(angle) * lightDirection.y,
-					lightDirection.z
-				));
+	function bakeLights() {
+		var curSel = sceneEditor.curEdit.elements;
+		sceneEditor.selectElements([]);
+		var passes = [];
+		for( m in scene.s3d.getMaterials() ) {
+			var s = m.getPass("shadow");
+			if( s != null && !s.isStatic ) passes.push(s);
+		}
+		for( p in passes )
+			p.isStatic = true;
+
+		function isDynamic(elt: hrt.prefab.Prefab) {
+			var p = elt;
+			while(p != null) {
+				if(p.name == "dynamic")
+					return true;
+				p = p.parent;
+			}
+			return false;
+		}
+
+		for(elt in data.flatten()) {
+			if(Std.is(elt, Instance) || isDynamic(elt)) {
+				var mats = sceneEditor.context.shared.getMaterials(elt);
+				for(mat in mats) {
+					var p = mat.getPass("shadow");
+					if(p != null)
+						p.isStatic = false;
+				}
+			}
+		}
+
+		scene.s3d.computeStatic();
+		for( p in passes )
+			p.isStatic = false;
+		var lights = data.getAll(hrt.prefab.Light);
+		for( l in lights ) {
+			if(!l.visible)
+				continue;
+			l.saveBaked(sceneEditor.context);
+		}
+		sceneEditor.selectElements(curSel);
+	}
+
+	function bakeVolumetricLightmaps(){
+		var volumetricLightmaps = data.getAll(hrt.prefab.vlm.VolumetricLightmap);
+		var total = 0;
+		for( v in volumetricLightmaps )
+			total += v.volumetricLightmap.getProbeCount();
+		if( total == 0 )
+			return;
+		if( !ide.confirm("Bake "+total+" probes?") )
+			return;
+		function bakeNext() {
+			var v = volumetricLightmaps.shift();
+			if( v == null ) {
+				ide.message("Done");
+				return;
 			}
 			}
+			v.startBake(sceneEditor.curEdit, bakeNext);
+			sceneEditor.selectElements([v]);
+		}
+		bakeNext();
+	}
+
+	function resetCamera( top : Bool ) {
+		var targetPt = new h3d.col.Point(0, 0, 0);
+		var curEdit = sceneEditor.curEdit;
+		if(curEdit != null && curEdit.rootObjects.length > 0) {
+			targetPt = curEdit.rootObjects[0].getAbsPos().getPosition().toPoint();
+		}
+		if(top)
+			sceneEditor.cameraController.set(200, Math.PI/2, 0.001, targetPt);
+		else
+			sceneEditor.cameraController.set(200, -4.7, 0.8, targetPt);
+		sceneEditor.cameraController.toTarget();
+	}
+
+	override function getDefaultContent() {
+		return haxe.io.Bytes.ofString(ide.toJSON(new hrt.prefab.Library().saveData()));
+	}
+
+	override function save() {
+		var content = ide.toJSON(data.saveData());
+		var newSign = haxe.crypto.Md5.encode(content);
+		if(newSign != currentSign)
+			haxe.Timer.delay(saveBackup.bind(content), 0);
+		currentSign = newSign;
+		sys.io.File.saveContent(getPath(), content);
+		super.save();
+	}
+
+	function updateGrid() {
+		if(grid != null) {
+			grid.remove();
+			grid = null;
+		}
+
+		if(!showGrid)
+			return;
+
+		grid = new h3d.scene.Graphics(scene.s3d);
+		grid.scale(1);
+		grid.material.mainPass.setPassName("debuggeom");
+		gridStep = ide.currentConfig.get("sceneeditor.gridStep");
+		gridSize = ide.currentConfig.get("sceneeditor.gridSize");
+
+		var col = h3d.Vector.fromColor(scene.engine.backgroundColor);
+		var hsl = col.toColorHSL();
+		if(hsl.z > 0.5) hsl.z -= 0.1;
+		else hsl.z += 0.1;
+		col.makeColor(hsl.x, hsl.y, hsl.z);
+
+		grid.lineStyle(1.0, col.toColor(), 1.0);
+		//width
+		for(i in 0...(hxd.Math.floor(gridSize / gridStep) + 1)) {
+			grid.moveTo(i * gridStep, 0, 0);
+			grid.lineTo(i * gridStep, gridSize, 0);
+
+			grid.moveTo(0, i * gridStep, 0);
+			grid.lineTo(gridSize, i * gridStep, 0);
+		}
+		grid.lineStyle(0);
+		grid.setPosition(-1 * gridSize / 2, -1 * gridSize / 2, 0);
+	}
+
+	function onUpdate(dt:Float) {
+		if(K.isDown(K.ALT)) {
+			posToolTip.visible = true;
+			var proj = sceneEditor.screenToGround(scene.s2d.mouseX, scene.s2d.mouseY);
+			posToolTip.text = proj != null ? '${Math.fmt(proj.x)}, ${Math.fmt(proj.y)}, ${Math.fmt(proj.z)}' : '???';
+			posToolTip.setPosition(scene.s2d.mouseX, scene.s2d.mouseY - 12);
+		}
+		else {
+			posToolTip.visible = false;
 		}
 		}
 
 
 		if( autoSync && (currentVersion != undo.currentID || lastSyncChange != properties.lastChange) ) {
 		if( autoSync && (currentVersion != undo.currentID || lastSyncChange != properties.lastChange) ) {
@@ -137,12 +631,152 @@ class Prefab extends FileView {
 			lastSyncChange = properties.lastChange;
 			lastSyncChange = properties.lastChange;
 			currentVersion = undo.currentID;
 			currentVersion = undo.currentID;
 		}
 		}
+
+	}
+
+	function onRefresh() {
 	}
 	}
 
 
 	override function onDragDrop(items : Array<String>, isDrop : Bool) {
 	override function onDragDrop(items : Array<String>, isDrop : Bool) {
-		return sceneEditor.onDragDrop(items,isDrop);
+		return sceneEditor.onDragDrop(items, isDrop);
+	}
+
+	function applyGraphicsFilters(typeid: String, enable: Bool)
+	{
+		saveDisplayState("graphicsFilters/" + typeid, enable);
+
+		var r : h3d.scene.Renderer = scene.s3d.renderer;
+
+		switch (typeid)
+		{
+		case "shadows":
+			r.shadows = enable;
+		default:
+		}
+	}
+
+	function applySceneFilter(typeid: String, visible: Bool) {
+		saveDisplayState("sceneFilters/" + typeid, visible);
+		var all = data.flatten(hrt.prefab.Prefab);
+		for(p in all) {
+			if(p.type == typeid || p.getCdbType() == typeid) {
+				sceneEditor.applySceneStyle(p);
+			}
+		}
+	}
+
+	function refreshSceneFilters() {
+		var filters : Array<String> = ide.currentConfig.get("sceneeditor.filterTypes");
+		filters = filters.copy();
+		for(sheet in DataFiles.getAvailableTypes()) {
+			filters.push(DataFiles.getTypeName(sheet));
+		}
+		sceneFilters = new Map();
+		for(f in filters) {
+			sceneFilters.set(f, getDisplayState("sceneFilters/" + f) != false);
+		}
+
+		var sceneFiltersMenu = layerToolbar.addMenu("", "Scene filters");
+		var content : Array<hide.comp.ContextMenu.ContextMenuItem> = [];
+		var initDone = false;
+		for(typeid in sceneFilters.keys()) {
+			content.push({label : typeid, checked : sceneFilters[typeid], click : function() {
+				var on = !sceneFilters[typeid];
+				sceneFilters.set(typeid, on);
+				if(initDone)
+					applySceneFilter(typeid, on);
+				content.find(function(item) return item.label == typeid).checked = on;
+			}});
+		}
+		sceneFiltersMenu.setContent(content);
+		initDone = true;
+	}
+
+	function initGraphicsFilters() {
+		for (typeid in graphicsFilters.keys())
+		{
+			applyGraphicsFilters(typeid, graphicsFilters.get(typeid));
+		}
 	}
 	}
 
 
-	// TOMORROW Comment this? but then the transition breaks existing tabs
-	// static var _ = FileTree.registerExtension(Prefab,["prefab"],{ icon : "sitemap" });
+	function refreshGraphicsFilters() {
+		var filters : Array<String> = ["shadows"];
+		filters = filters.copy();
+		graphicsFilters = new Map();
+		for(f in filters) {
+			graphicsFilters.set(f, getDisplayState("graphicsFilters/" + f) != false);
+		}
+		var graphicsFiltersMenu = layerToolbar.addMenu("", "Graphics filters");
+		var content : Array<hide.comp.ContextMenu.ContextMenuItem> = [];
+		var initDone = false;
+		for(typeid in graphicsFilters.keys()) {
+			content.push({label : typeid, checked : graphicsFilters[typeid], click : function() {
+				var on = !graphicsFilters[typeid];
+				graphicsFilters.set(typeid, on);
+				if(initDone)
+					applyGraphicsFilters(typeid, on);
+				content.find(function(item) return item.label == typeid).checked = on;
+			}});
+		}
+		graphicsFiltersMenu.setContent(content);
+		initDone = true;
+	}
+
+	function applyTreeStyle(p: PrefabElement, el: Element, pname: String) {
+	}
+
+	function onPrefabChange(p: PrefabElement, ?pname: String) {
+
+	}
+
+	function applySceneStyle(p: PrefabElement) {
+		var prefabView = Std.downcast(p, hrt.prefab.Library); // don't use "to" (Reference)
+		if( prefabView != null && prefabView.parent == null ) {
+			updateGrid();
+			return;
+		}
+
+		var obj3d = p.to(Object3D);
+		if(obj3d != null) {
+			var visible = obj3d.visible && !sceneEditor.isHidden(obj3d) && sceneFilters.get(p.type) != false;
+			if(visible) {
+				var cdbType = p.getCdbType();
+				if(cdbType != null && sceneFilters.get(cdbType) == false)
+					visible = false;
+			}
+			for(ctx in sceneEditor.getContexts(obj3d)) {
+				ctx.local3d.visible = visible;
+			}
+		}
+		var color = getDisplayColor(p);
+		if(color != null){
+			color = (color & 0xffffff) | 0xa0000000;
+			var box = p.to(hrt.prefab.l3d.Box);
+			if(box != null) {
+				var ctx = sceneEditor.getContext(box);
+				box.setColor(ctx, color);
+			}
+			var poly = p.to(hrt.prefab.l3d.Polygon);
+			if(poly != null) {
+				var ctx = sceneEditor.getContext(poly);
+				poly.setColor(ctx, color);
+			}
+		}
+	}
+
+	function getDisplayColor(p: PrefabElement) : Null<Int> {
+		var typeId = p.getCdbType();
+		if(typeId != null) {
+			var colors = ide.currentConfig.get("sceneeditor.colors");
+			var color = Reflect.field(colors, typeId);
+			if(color != null) {
+				return Std.parseInt("0x"+color.substr(1)) | 0xff000000;
+			}
+		}
+		return null;
+	}
+
+	static var _ = FileTree.registerExtension(Prefab, ["prefab"], { icon : "sitemap", createNew : "Prefab" });
+	static var _1 = FileTree.registerExtension(Prefab, ["l3d"], { icon : "sitemap" });
+
 }
 }

+ 2 - 2
hide/view/l3d/CameraController2D.hx

@@ -106,7 +106,7 @@ class CameraController2D extends h2d.Object {
 		case EWheel:
 		case EWheel:
 			zoom(e.wheelDelta);
 			zoom(e.wheelDelta);
 		case EPush:
 		case EPush:
-			@:privateAccess scene.events.startDrag(onEvent, function() pushing = -1, e);
+			@:privateAccess scene.events.startCapture(onEvent, function() pushing = -1, e.touchId);
 			pushing = e.button;
 			pushing = e.button;
 			pushTime = haxe.Timer.stamp();
 			pushTime = haxe.Timer.stamp();
 			pushStartX = pushX = e.relX;
 			pushStartX = pushX = e.relX;
@@ -114,7 +114,7 @@ class CameraController2D extends h2d.Object {
 		case ERelease, EReleaseOutside:
 		case ERelease, EReleaseOutside:
 			if( pushing == e.button ) {
 			if( pushing == e.button ) {
 				pushing = -1;
 				pushing = -1;
-				@:privateAccess scene.events.stopDrag();
+				@:privateAccess scene.events.stopCapture();
 				if( e.kind == ERelease && haxe.Timer.stamp() - pushTime < 0.2 && hxd.Math.distance(e.relX - pushStartX,e.relY - pushStartY) < 5 )
 				if( e.kind == ERelease && haxe.Timer.stamp() - pushTime < 0.2 && hxd.Math.distance(e.relX - pushStartX,e.relY - pushStartY) < 5 )
 					onClick(e);
 					onClick(e);
 			}
 			}

+ 2 - 2
hide/view/l3d/Gizmo2D.hx

@@ -68,11 +68,11 @@ class Gizmo2D extends h2d.Object {
 		var center = localToGlobal();
 		var center = localToGlobal();
 		moving = true;
 		moving = true;
 		onStartMove(t);
 		onStartMove(t);
-		int.startDrag(function(e:hxd.Event) {
+		int.startCapture(function(e:hxd.Event) {
 			switch( e.kind ) {
 			switch( e.kind ) {
 			case ERelease, EReleaseOutside:
 			case ERelease, EReleaseOutside:
 				moving = false;
 				moving = false;
-				int.stopDrag();
+				int.stopCapture();
 				onFinishMove();
 				onFinishMove();
 			case EMove:
 			case EMove:
 				var dx = scene.mouseX - dragStartX;
 				var dx = scene.mouseX - dragStartX;

+ 0 - 749
hide/view/l3d/Level3D.hx

@@ -1,749 +0,0 @@
-package hide.view.l3d;
-using Lambda;
-
-import hxd.Math;
-import hxd.Key as K;
-
-import hrt.prefab.Prefab as PrefabElement;
-import hrt.prefab.Object3D;
-import hrt.prefab.l3d.Instance;
-import hide.comp.cdb.DataFiles;
-
-
-@:access(hide.view.l3d.Level3D)
-class CamController extends h3d.scene.CameraController {
-	public var groundSnapAngle = hxd.Math.degToRad(30);
-	var prefab : Level3D;
-	var startPush : h2d.col.Point;
-	var moveCount = 0;
-
-	public function new(parent, prefab) {
-		super(null, parent);
-		this.prefab = prefab;
-	}
-
-	override function onEvent( e : hxd.Event ) {
-		if(curPos == null) return;
-		switch( e.kind ) {
-		case EWheel:
-			zoom(e.wheelDelta);
-		case EPush:
-			pushing = e.button;
-			pushTime = haxe.Timer.stamp();
-			pushStartX = pushX = e.relX;
-			pushStartY = pushY = e.relY;
-			startPush = new h2d.col.Point(pushX, pushY);
-			if( pushing == 2 ) {
-				var se = prefab.sceneEditor;
-				var selection = se.getSelection();
-				var angle = hxd.Math.abs(Math.PI/2 - phi);
-				if( selection.length == 0 && angle > groundSnapAngle ) {
-					var visGround = se.screenToGround(se.scene.s2d.width / 2, se.scene.s2d.height / 2);
-					var dist = se.screenDistToGround(se.scene.s2d.width / 2, se.scene.s2d.height / 2);
-					if( dist != null ) {
-						set(dist, null, null, visGround);
-					}
-				}
-			}
-			moveCount = 0;
-			@:privateAccess scene.window.mouseLock = true;
-		case ERelease, EReleaseOutside:
-			if( pushing == e.button ) {
-				pushing = -1;
-				startPush = null;
-				if( e.kind == ERelease && haxe.Timer.stamp() - pushTime < 0.2 && hxd.Math.distance(e.relX - pushStartX,e.relY - pushStartY) < 5 )
-					onClick(e);
-				@:privateAccess scene.window.mouseLock = false;
-			}
-		case EMove:
-			// Windows bug that jumps movementX/Y on all browsers
-			if( moveCount < 10 && Math.distanceSq(pushX - e.relX, pushY - e.relY) > 100000 ) {
-				pushX = e.relX;
-				pushY = e.relY;
-				return;
-			}
-			moveCount++;
-
-			switch( pushing ) {
-			case 1:
-				if(startPush != null && startPush.distance(new h2d.col.Point(e.relX, e.relY)) > 3) {
-					var angle = hxd.Math.abs(Math.PI/2 - phi);
-					if(K.isDown(K.SHIFT) || angle < groundSnapAngle) {
-						var m = 0.001 * curPos.x * panSpeed / 25;
-						pan(-(e.relX - pushX) * m, (e.relY - pushY) * m);
-					}
-					else {
-						var se = prefab.sceneEditor;
-						var fromPt = se.screenToGround(pushX, pushY);
-						var toPt = se.screenToGround(e.relX, e.relY);
-						if(fromPt == null || toPt == null)
-							return;
-						var delta = toPt.sub(fromPt).toVector();
-						delta.w = 0;
-						targetOffset = targetOffset.sub(delta);
-					}
-				}
-				pushX = e.relX;
-				pushY = e.relY;
-			case 2:
-				rot(e.relX - pushX, e.relY - pushY);
-				pushX = e.relX;
-				pushY = e.relY;
-			default:
-			}
-		case EFocus:
-			@:privateAccess scene.window.mouseLock = false;
-		default:
-		}
-	}
-
-	function moveKeys() {
-		var mov = new h3d.Vector();
-		if( K.isDown(K.UP) || K.isDown(K.Z) || K.isDown(K.W) )
-			mov.x += 1;
-		if( K.isDown(K.DOWN) || K.isDown(K.S) )
-			mov.x -= 1;
-		if( K.isDown(K.LEFT) || K.isDown(K.Q) || K.isDown(K.A) )
-			mov.y -= 1;
-		if( K.isDown(K.RIGHT) || K.isDown(K.D) )
-			mov.y += 1;
-
-		if( mov.x == 0 && mov.y == 0 )
-			return;
-		var dir = new h3d.Vector(
-			mov.x * Math.cos(theta) + mov.y * Math.cos(Math.PI / 2 + theta),
-			mov.x * Math.sin(theta) + mov.y * Math.sin(Math.PI / 2 + theta)
-		);
-		var moveSpeed = Ide.inst.currentConfig.get("l3d.camera.moveSpeed", 1.5);
-
-		var delta = dir.multiply(0.01 * moveSpeed * (distance + scene.camera.zNear));
-		delta.w = 0;
-		targetOffset = targetOffset.sub(delta);
-	}
-
-	override function sync(ctx : h3d.scene.RenderContext) {
-		if( pushing == 2 ) {
-			moveKeys();
-		}
-		super.sync(ctx);
-	}
-}
-
-@:access(hide.view.l3d.Level3D)
-private class PrefabSceneEditor extends hide.comp.SceneEditor {
-	var parent : Level3D;
-
-	public function new(view, data) {
-		super(view, data);
-		parent = cast view;
-		this.localTransform = false; // TODO: Expose option
-	}
-
-	override function makeCamController() {
-		var c = new CamController(scene.s3d, parent);
-		c.friction = 0.9;
-		c.panSpeed = 0.6;
-		c.zoomAmount = 1.05;
-		c.smooth = 0.7;
-		c.minDistance = 1;
-		return c;
-	}
-
-	override function refresh(?mode, ?callback) {
-		parent.onRefresh();
-		super.refresh(mode, callback);
-	}
-
-	override function update(dt) {
-		super.update(dt);
-		parent.onUpdate(dt);
-	}
-
-	override function onSceneReady() {
-		super.onSceneReady();
-		parent.onSceneReady();
-	}
-
-	override function applyTreeStyle(p: PrefabElement, el: Element, ?pname: String) {
-		super.applyTreeStyle(p, el, pname);
-		parent.applyTreeStyle(p, el, pname);
-	}
-
-	override function applySceneStyle(p:PrefabElement) {
-		parent.applySceneStyle(p);
-	}
-
-	override function onPrefabChange(p: PrefabElement, ?pname: String) {
-		super.onPrefabChange(p, pname);
-		parent.onPrefabChange(p, pname);
-	}
-
-	override function getNewContextMenu(current: PrefabElement, ?onMake: PrefabElement->Void=null, ?groupByType = true ) {
-		var newItems = super.getNewContextMenu(current, onMake, groupByType);
-
-		function setup(p : PrefabElement) {
-			autoName(p);
-			haxe.Timer.delay(addElements.bind([p]), 0);
-		}
-
-		function addNewInstances() {
-			var items = new Array<hide.comp.ContextMenu.ContextMenuItem>();
-			for(type in DataFiles.getAvailableTypes() ) {
-				var typeId = DataFiles.getTypeName(type);
-				var label = typeId.charAt(0).toUpperCase() + typeId.substr(1);
-
-				var refCols = Instance.findRefColumns(type);
-				var refSheet = refCols == null ? null : type.base.getSheet(refCols.sheet);
-				var idCol = refCols == null ? null : Instance.findIDColumn(refSheet);
-
-				function make(name) {
-					var p = new hrt.prefab.l3d.Instance(current == null ? sceneData : current);
-					p.name = name;
-					p.props = makeCdbProps(p, type);
-					setup(p);
-					if(onMake != null)
-						onMake(p);
-					return p;
-				}
-
-				if(idCol != null && refSheet.props.dataFiles == null ) {
-					var kindItems = new Array<hide.comp.ContextMenu.ContextMenuItem>();
-					for(line in refSheet.lines) {
-						var kind : String = Reflect.getProperty(line, idCol.name);
-						kindItems.push({
-							label : kind,
-							click : function() {
-								var p = make(kind.charAt(0).toLowerCase() + kind.substr(1));
-								var obj : Dynamic = p.props;
-								for( c in refCols.cols ) {
-									if( c == refCols.cols[refCols.cols.length-1] )
-										Reflect.setField(obj, c.name, kind);
-									else {
-										var s = Reflect.field(obj,c.name);
-										if( s == null ) {
-											s = {};
-											Reflect.setField(obj, c.name, s);
-										}
-										obj = s;
-									}
-								}
-							}
-						});
-					}
-					items.unshift({
-						label : label,
-						menu: kindItems
-					});
-				}
-				else {
-					items.push({
-						label : label,
-						click : make.bind(typeId)
-					});
-				}
-			}
-			newItems.unshift({
-				label : "Instance",
-				menu: items
-			});
-		};
-		addNewInstances();
-		return newItems;
-	}
-
-	override function getAvailableTags(p:PrefabElement) {
-		return cast ide.currentConfig.get("l3d.tags");
-	}
-}
-
-// TODO TOMORROW rename to Prefab
-class Level3D extends FileView {
-
-	public var sceneEditor : PrefabSceneEditor;
-	var data : hrt.prefab.Library;
-
-	var tools : hide.comp.Toolbar;
-
-	var layerToolbar : hide.comp.Toolbar;
-	var layerButtons : Map<PrefabElement, hide.comp.Toolbar.ToolToggle>;
-
-	var grid : h3d.scene.Graphics;
-
-	var gridStep : Int;
-	var gridSize : Int;
-	var showGrid = false;
-
-	// autoSync
-	var autoSync : Bool;
-	var currentVersion : Int = 0;
-	var lastSyncChange : Float = 0.;
-	var sceneFilters : Map<String, Bool>;
-	var graphicsFilters : Map<String, Bool>;
-	var statusText : h2d.Text;
-	var posToolTip : h2d.Text;
-
-	var scene(get, null):  hide.comp.Scene;
-	function get_scene() return sceneEditor.scene;
-	public var properties(get, null):  hide.comp.PropsEditor;
-	function get_properties() return sceneEditor.properties;
-
-	override function onDisplay() {
-		if( sceneEditor != null ) sceneEditor.dispose();
-
-		data = new hrt.prefab.Library();
-		var content = sys.io.File.getContent(getPath());
-		data.loadData(haxe.Json.parse(content));
-		currentSign = haxe.crypto.Md5.encode(content);
-
-		element.html('
-			<div class="flex vertical">
-				<div style="flex: 0 0 30px;">
-					<span class="tools-buttons"></span>
-					<span class="layer-buttons"></span>
-				</div>
-
-				<div class="scene-partition" style="display: flex; flex-direction: row; flex: 1; overflow: hidden;">
-					<div class="heaps-scene"></div>
-
-					<div class="tree-column">
-						<div class="flex vertical">
-							<div class="hide-toolbar">
-								<div class="toolbar-label">
-									<div class="icon fa fa-sitemap"></div>
-									Scene
-								</div>
-								<div class="button collapse-btn" title="Collapse all">
-									<div class="icon fa fa-reply-all"></div>
-								</div>
-
-								<div class="button combine-btn layout-btn" title="Move to bottom of tree">
-									<div class="icon fa fa-compress"></div>
-								</div>
-								<div class="button separate-btn layout-btn" title="Move to own column">
-									<div class="icon fa fa-expand"></div>
-								</div>
-
-								<div class="button hide-cols-btn close-btn" title="Hide Tree & Props">
-									<div class="icon fa fa-chevron-right"></div>
-								</div>
-							</div>
-
-							<div class="hide-scenetree"></div>
-						</div>
-					</div>
-
-					<div class="props-column">
-						<div class="hide-toolbar">
-							<div class="toolbar-label">
-								<div class="icon fa fa-sitemap"></div>
-								Properties
-							</div>
-						</div>
-							<div class="hide-scroll"></div>
-					</div>
-
-					<div class="button show-cols-btn close-btn" title="Show Tree & Props">
-						<div class="icon fa fa-chevron-left"></div>
-					</div>
-				</div>
-			</div>
-		');
-
-		tools = new hide.comp.Toolbar(null,element.find(".tools-buttons"));
-		layerToolbar = new hide.comp.Toolbar(null,element.find(".layer-buttons"));
-		currentVersion = undo.currentID;
-
-		sceneEditor = new PrefabSceneEditor(this, data);
-		element.find(".hide-scenetree").first().append(sceneEditor.tree.element);
-		element.find(".hide-scroll").first().append(properties.element);
-		element.find(".heaps-scene").first().append(scene.element);
-		sceneEditor.tree.element.addClass("small");
-
-		element.find(".show-cols-btn").first().hide();
-		element.find(".show-cols-btn").first().click(function(_) {
-			element.find(".tree-column").first().show();
-			element.find(".props-column").first().show();
-			element.find(".show-cols-btn").first().hide();
-			@:privateAccess scene.window.checkResize();
-		});
-		element.find(".hide-cols-btn").first().click(function(_) {
-			element.find(".tree-column").first().hide();
-			element.find(".props-column").first().hide();
-			element.find(".show-cols-btn").first().show();
-			@:privateAccess scene.window.checkResize();
-		});
-		function setCombine(val) {
-			var fullscene = element.find(".scene-partition").first();
-			var props = element.find(".props-column").first();
-			fullscene.toggleClass("reduced-columns", val);
-			if( val ) {
-				element.find(".hide-scenetree").first().parent().append(props);
-				element.find(".combine-btn").first().hide();
-				element.find(".separate-btn").first().show();
-			} else {
-				fullscene.append(props);
-				element.find(".combine-btn").first().show();
-				element.find(".separate-btn").first().hide();
-			}
-			@:privateAccess
-			if( scene.window != null)
-				scene.window.checkResize();
-		}
-
-		element.find(".combine-btn").first().click((_) -> setCombine(true));
-		element.find(".separate-btn").first().click((_) -> setCombine(false));
-		setCombine(false);
-
-		element.find(".collapse-btn").click(function(e) {
-			sceneEditor.collapseTree();
-		});
-
-		refreshSceneFilters();
-		refreshGraphicsFilters();
-	}
-
-	public function onSceneReady() {
-		tools.saveDisplayKey = "Prefab/toolbar";
-		statusText = new h2d.Text(hxd.res.DefaultFont.get(), scene.s2d);
-		statusText.setPosition(5, 5);
-		statusText.visible = false;
-		hide.comp.Toolbar.ToolsObject.level3D = this;
-		var toolsNames : Iterator<String> = hide.comp.Toolbar.ToolsObject.tools.keys();
-		for (toolName in toolsNames) {
-			var tool = hide.comp.Toolbar.ToolsObject.tools.get(toolName);
-			if (tool != null) {
-				var shortcut = (config.get("key.sceneeditor." + toolName) != null)? " (" + config.get("key.sceneeditor." + toolName) + ")" : "";
-				switch(tool.type) {
-					case Button:
-						var button = tools.addButton(tool.icon, tool.title + shortcut, tool.buttonFunction);
-						if (tool.iconTransform != null) {
-							button.find(".icon").css({transform: tool.iconTransform});
-						}
-					case Toggle:
-						var toggle = tools.addToggle(tool.icon, tool.title + shortcut, tool.toggleFunction);
-						if (tool.iconTransform != null) {
-							toggle.element.find(".icon").css({transform: tool.iconTransform});
-						}
-						keys.register("sceneeditor." + toolName, () -> toggle.toggle(!toggle.isDown()));
-						if (tool.rightClick != null) {
-							toggle.rightClick(tool.rightClick);
-						}
-					case Color:
-						tools.addColor(tool.title, tool.colorFunction);
-					case Range:
-						tools.addRange(tool.title, tool.rangeFunction, 1.);
-					case Menu:
-						var menu = tools.addMenu(tool.icon, tool.title);
-						menu.setContent(tool.menuItems());
-				}
-			}
-		}
-		posToolTip = new h2d.Text(hxd.res.DefaultFont.get(), scene.s2d);
-		posToolTip.dropShadow = { dx : 1, dy : 1, color : 0, alpha : 0.5 };
-
-		updateStats();
-		updateGrid();
-		initGraphicsFilters();
-	}
-
-	function updateStats() {
-		var memStats = scene.engine.mem.stats();
-		@:privateAccess
-		var lines : Array<String> = [
-			'Scene objects: ${scene.s3d.getObjectsCount()}',
-			'Interactives: ' + sceneEditor.interactives.count(),
-			'Contexts: ' + sceneEditor.context.shared.contexts.count(),
-			'Triangles: ${scene.engine.drawTriangles}',
-			'Buffers: ${memStats.bufferCount}',
-			'Textures: ${memStats.textureCount}',
-			'FPS: ${Math.round(scene.engine.realFps)}',
-			'Draw Calls: ${scene.engine.drawCalls}',
-		];
-		statusText.text = lines.join("\n");
-		haxe.Timer.delay(function() sceneEditor.event.wait(0.5, updateStats), 0);
-	}
-
-	function bakeLights() {
-		var curSel = sceneEditor.curEdit.elements;
-		sceneEditor.selectElements([]);
-		var passes = [];
-		for( m in scene.s3d.getMaterials() ) {
-			var s = m.getPass("shadow");
-			if( s != null && !s.isStatic ) passes.push(s);
-		}
-		for( p in passes )
-			p.isStatic = true;
-
-		function isDynamic(elt: hrt.prefab.Prefab) {
-			var p = elt;
-			while(p != null) {
-				if(p.name == "dynamic")
-					return true;
-				p = p.parent;
-			}
-			return false;
-		}
-
-		for(elt in data.flatten()) {
-			if(Std.is(elt, Instance) || isDynamic(elt)) {
-				var mats = sceneEditor.context.shared.getMaterials(elt);
-				for(mat in mats) {
-					var p = mat.getPass("shadow");
-					if(p != null)
-						p.isStatic = false;
-				}
-			}
-		}
-
-		scene.s3d.computeStatic();
-		for( p in passes )
-			p.isStatic = false;
-		var lights = data.getAll(hrt.prefab.Light);
-		for( l in lights ) {
-			if(!l.visible)
-				continue;
-			l.saveBaked(sceneEditor.context);
-		}
-		sceneEditor.selectElements(curSel);
-	}
-
-	function bakeVolumetricLightmaps(){
-		var volumetricLightmaps = data.getAll(hrt.prefab.vlm.VolumetricLightmap);
-		var total = 0;
-		for( v in volumetricLightmaps )
-			total += v.volumetricLightmap.getProbeCount();
-		if( total == 0 )
-			return;
-		if( !ide.confirm("Bake "+total+" probes?") )
-			return;
-		function bakeNext() {
-			var v = volumetricLightmaps.shift();
-			if( v == null ) {
-				ide.message("Done");
-				return;
-			}
-			v.startBake(sceneEditor.curEdit, bakeNext);
-			sceneEditor.selectElements([v]);
-		}
-		bakeNext();
-	}
-
-	function resetCamera( top : Bool ) {
-		var targetPt = new h3d.col.Point(0, 0, 0);
-		var curEdit = sceneEditor.curEdit;
-		if(curEdit != null && curEdit.rootObjects.length > 0) {
-			targetPt = curEdit.rootObjects[0].getAbsPos().getPosition().toPoint();
-		}
-		if(top)
-			sceneEditor.cameraController.set(200, Math.PI/2, 0.001, targetPt);
-		else
-			sceneEditor.cameraController.set(200, -4.7, 0.8, targetPt);
-		sceneEditor.cameraController.toTarget();
-	}
-
-	override function getDefaultContent() {
-		return haxe.io.Bytes.ofString(ide.toJSON(new hrt.prefab.Library().saveData()));
-	}
-
-	override function save() {
-		var content = ide.toJSON(data.saveData());
-		var newSign = haxe.crypto.Md5.encode(content);
-		if(newSign != currentSign)
-			haxe.Timer.delay(saveBackup.bind(content), 0);
-		currentSign = newSign;
-		sys.io.File.saveContent(getPath(), content);
-		super.save();
-	}
-
-	function updateGrid() {
-		if(grid != null) {
-			grid.remove();
-			grid = null;
-		}
-
-		if(!showGrid)
-			return;
-
-		grid = new h3d.scene.Graphics(scene.s3d);
-		grid.scale(1);
-		grid.material.mainPass.setPassName("debuggeom");
-		gridStep = ide.currentConfig.get("l3d.gridStep");
-		gridSize = ide.currentConfig.get("l3d.gridSize");
-
-		var col = h3d.Vector.fromColor(scene.engine.backgroundColor);
-		var hsl = col.toColorHSL();
-		if(hsl.z > 0.5) hsl.z -= 0.1;
-		else hsl.z += 0.1;
-		col.makeColor(hsl.x, hsl.y, hsl.z);
-
-		grid.lineStyle(1.0, col.toColor(), 1.0);
-		//width
-		for(i in 0...(hxd.Math.floor(gridSize / gridStep) + 1)) {
-			grid.moveTo(i * gridStep, 0, 0);
-			grid.lineTo(i * gridStep, gridSize, 0);
-
-			grid.moveTo(0, i * gridStep, 0);
-			grid.lineTo(gridSize, i * gridStep, 0);
-		}
-		grid.lineStyle(0);
-		grid.setPosition(-1 * gridSize / 2, -1 * gridSize / 2, 0);
-	}
-
-	function onUpdate(dt:Float) {
-		if(K.isDown(K.ALT)) {
-			posToolTip.visible = true;
-			var proj = sceneEditor.screenToGround(scene.s2d.mouseX, scene.s2d.mouseY);
-			posToolTip.text = proj != null ? '${Math.fmt(proj.x)}, ${Math.fmt(proj.y)}, ${Math.fmt(proj.z)}' : '???';
-			posToolTip.setPosition(scene.s2d.mouseX, scene.s2d.mouseY - 12);
-		}
-		else {
-			posToolTip.visible = false;
-		}
-
-		if( autoSync && (currentVersion != undo.currentID || lastSyncChange != properties.lastChange) ) {
-			save();
-			lastSyncChange = properties.lastChange;
-			currentVersion = undo.currentID;
-		}
-
-	}
-
-	function onRefresh() {
-	}
-
-	override function onDragDrop(items : Array<String>, isDrop : Bool) {
-		return sceneEditor.onDragDrop(items, isDrop);
-	}
-
-	function applyGraphicsFilters(typeid: String, enable: Bool)
-	{
-		saveDisplayState("graphicsFilters/" + typeid, enable);
-
-		var r : h3d.scene.Renderer = scene.s3d.renderer;
-
-		switch (typeid)
-		{
-		case "shadows":
-			r.shadows = enable;
-		default:
-		}
-	}
-
-	function applySceneFilter(typeid: String, visible: Bool) {
-		saveDisplayState("sceneFilters/" + typeid, visible);
-		var all = data.flatten(hrt.prefab.Prefab);
-		for(p in all) {
-			if(p.type == typeid || p.getCdbType() == typeid) {
-				sceneEditor.applySceneStyle(p);
-			}
-		}
-	}
-
-	function refreshSceneFilters() {
-		var filters : Array<String> = ide.currentConfig.get("l3d.filterTypes");
-		filters = filters.copy();
-		for(sheet in DataFiles.getAvailableTypes()) {
-			filters.push(DataFiles.getTypeName(sheet));
-		}
-		sceneFilters = new Map();
-		for(f in filters) {
-			sceneFilters.set(f, getDisplayState("sceneFilters/" + f) != false);
-		}
-	}
-
-	function initGraphicsFilters() {
-		for (typeid in graphicsFilters.keys())
-		{
-			applyGraphicsFilters(typeid, graphicsFilters.get(typeid));
-		}
-	}
-
-	function refreshGraphicsFilters() {
-		var filters : Array<String> = ["shadows"];
-		filters = filters.copy();
-		graphicsFilters = new Map();
-		for(f in filters) {
-			graphicsFilters.set(f, getDisplayState("graphicsFilters/" + f) != false);
-		}
-	}
-
-	function filtersToMenuItem(filters : Map<String, Bool>, type : String) : Array<hide.comp.ContextMenu.ContextMenuItem> {
-		var content : Array<hide.comp.ContextMenu.ContextMenuItem> = [];
-		var initDone = false;
-		for(typeid in filters.keys()) {
-			content.push({label : typeid, checked : filters[typeid], click : function() {
-				var on = !filters[typeid];
-				filters.set(typeid, on);
-				if(initDone)
-					switch (type){
-						case "Graphics":
-							applyGraphicsFilters(typeid, on);
-						case "Scene":
-							applySceneFilter(typeid, on);
-					}
-					
-				content.find(function(item) return item.label == typeid).checked = on;
-			}});
-		}
-		initDone = true;
-		return content;
-	}
-
-	function applyTreeStyle(p: PrefabElement, el: Element, pname: String) {
-	}
-
-	function onPrefabChange(p: PrefabElement, ?pname: String) {
-
-	}
-
-	function applySceneStyle(p: PrefabElement) {
-		var prefabView = Std.downcast(p, hrt.prefab.Library); // don't use "to" (Reference)
-		if( prefabView != null && prefabView.parent == null ) {
-			updateGrid();
-			return;
-		}
-
-		var obj3d = p.to(Object3D);
-		if(obj3d != null) {
-			var visible = obj3d.visible && !sceneEditor.isHidden(obj3d) && sceneFilters.get(p.type) != false;
-			if(visible) {
-				var cdbType = p.getCdbType();
-				if(cdbType != null && sceneFilters.get(cdbType) == false)
-					visible = false;
-			}
-			for(ctx in sceneEditor.getContexts(obj3d)) {
-				ctx.local3d.visible = visible;
-			}
-		}
-		var color = getDisplayColor(p);
-		if(color != null){
-			color = (color & 0xffffff) | 0xa0000000;
-			var box = p.to(hrt.prefab.l3d.Box);
-			if(box != null) {
-				var ctx = sceneEditor.getContext(box);
-				box.setColor(ctx, color);
-			}
-			var poly = p.to(hrt.prefab.l3d.Polygon);
-			if(poly != null) {
-				var ctx = sceneEditor.getContext(poly);
-				poly.setColor(ctx, color);
-			}
-		}
-	}
-
-	function getDisplayColor(p: PrefabElement) : Null<Int> {
-		var typeId = p.getCdbType();
-		if(typeId != null) {
-			var colors = ide.currentConfig.get("l3d.colors");
-			var color = Reflect.field(colors, typeId);
-			if(color != null) {
-				return Std.parseInt("0x"+color.substr(1)) | 0xff000000;
-			}
-		}
-		return null;
-	}
-
-	static var _ = FileTree.registerExtension(Level3D, ["prefab"], { icon : "sitemap", createNew : "Prefab" });
-	static var _1 = FileTree.registerExtension(Level3D, ["l3d"], { icon : "sitemap" });
-
-}

+ 103 - 53
hrt/prefab/fx/Emitter.hx

@@ -48,6 +48,7 @@ typedef InstanceDef = {
 	worldSpeed: Value,
 	worldSpeed: Value,
 	startSpeed: Value,
 	startSpeed: Value,
 	startWorldSpeed: Value,
 	startWorldSpeed: Value,
+	orbitSpeed: Value,
 	acceleration: Value,
 	acceleration: Value,
 	worldAcceleration: Value,
 	worldAcceleration: Value,
 	localOffset: Value,
 	localOffset: Value,
@@ -94,18 +95,40 @@ private class ParticleTransform {
 		qRot.load(quat);
 		qRot.load(quat);
 	}
 	}
 
 
-	public function setPosition( x, y, z ) {
+	public inline function setPosition( x, y, z ) {
 		this.x = x;
 		this.x = x;
 		this.y = y;
 		this.y = y;
 		this.z = z;
 		this.z = z;
 	}
 	}
 
 
-	public function setScale( x, y, z ) {
+	public inline function transform3x3( m : h3d.Matrix ) {
+		var px = x * m._11 + y * m._21 + z * m._31;
+		var py = x * m._12 + y * m._22 + z * m._32;
+		var pz = x * m._13 + y * m._23 + z * m._33;
+		x = px;
+		y = py;
+		z = pz;
+	}
+
+	public inline function setScale( x, y, z ) {
 		this.scaleX = x;
 		this.scaleX = x;
 		this.scaleY = y;
 		this.scaleY = y;
 		this.scaleZ = z;
 		this.scaleZ = z;
 	}
 	}
 
 
+	public inline function getWorldPosition() {
+		var ppos = parent.getAbsPos();
+		return new h3d.Vector(x + ppos.tx, y + ppos.ty, z + ppos.tz);
+	}
+
+	public inline function setWorldPosition(v: h3d.Vector) {
+		var ppos = parent.getAbsPos();
+		x = v.x - ppos.tx;
+		y = v.y - ppos.ty;
+		z = v.z - ppos.tz;
+	}
+
+
 	public function calcAbsPos() {
 	public function calcAbsPos() {
 		qRot.toMatrix(absPos);
 		qRot.toMatrix(absPos);
 		absPos._11 *= scaleX;
 		absPos._11 *= scaleX;
@@ -146,12 +169,11 @@ private class ParticleInstance  {
 
 
 	var emitter : EmitterObject;
 	var emitter : EmitterObject;
 	var evaluator : Evaluator;
 	var evaluator : Evaluator;
-	var parent : h3d.scene.Object;
 
 
 	var transform = new ParticleTransform();
 	var transform = new ParticleTransform();
-	var childTransform = new ParticleTransform();
+	var localTransform = new ParticleTransform();
 	public var absPos = new h3d.Matrix();
 	public var absPos = new h3d.Matrix();
-	public var childMat = new h3d.Matrix();
+	public var localMat = new h3d.Matrix();
 	public var baseMat : h3d.Matrix;
 	public var baseMat : h3d.Matrix;
 
 
 	public var startTime = 0.0;
 	public var startTime = 0.0;
@@ -170,7 +192,7 @@ private class ParticleInstance  {
 
 
 	public function init(emitter: EmitterObject, def: InstanceDef) {
 	public function init(emitter: EmitterObject, def: InstanceDef) {
 		transform.reset();
 		transform.reset();
-		childTransform.reset();
+		localTransform.reset();
 		life = 0;
 		life = 0;
 		lifeTime = 0;
 		lifeTime = 0;
 		startFrame = 0;
 		startFrame = 0;
@@ -190,7 +212,7 @@ private class ParticleInstance  {
 	}
 	}
 
 
 	public function dispose() {
 	public function dispose() {
-		transform.parent = childTransform.parent = null;
+		transform.parent = localTransform.parent = null;
 		emitter = null;
 		emitter = null;
 	}
 	}
 
 
@@ -204,6 +226,7 @@ private class ParticleInstance  {
 	static var tmpSpeed = new h3d.Vector();
 	static var tmpSpeed = new h3d.Vector();
 	static var tmpMat = new h3d.Matrix();
 	static var tmpMat = new h3d.Matrix();
 	static var tmpPos = new h3d.Vector();
 	static var tmpPos = new h3d.Vector();
+	static var tmpPos2 = new h3d.Vector();
 	static var tmpCamRotAxis = new h3d.Vector();
 	static var tmpCamRotAxis = new h3d.Vector();
 	static var tmpCamAlign = new h3d.Vector();
 	static var tmpCamAlign = new h3d.Vector();
 	static var tmpCamVec = new h3d.Vector();
 	static var tmpCamVec = new h3d.Vector();
@@ -232,47 +255,52 @@ private class ParticleInstance  {
 	}
 	}
 
 
 	public function update( dt : Float ) {
 	public function update( dt : Float ) {
-
 		var t = hxd.Math.clamp(life / lifeTime, 0.0, 1.0);
 		var t = hxd.Math.clamp(life / lifeTime, 0.0, 1.0);
+		tmpSpeed.set(0,0,0);
 
 
 		if( life == 0 ) {
 		if( life == 0 ) {
 			// START LOCAL SPEED
 			// START LOCAL SPEED
 			evaluator.getVector(def.startSpeed, 0.0, tmpSpeedAccumulation);
 			evaluator.getVector(def.startSpeed, 0.0, tmpSpeedAccumulation);
-			if(tmpSpeedAccumulation.lengthSq() > 0.001)
-				tmpSpeedAccumulation.transform3x3(orientation.toMatrix(tmpMat));
+			tmpSpeedAccumulation.transform3x3(orientation.toMatrix(tmpMat));
 			add(speedAccumulation, tmpSpeedAccumulation);
 			add(speedAccumulation, tmpSpeedAccumulation);
 			// START WORLD SPEED
 			// START WORLD SPEED
 			evaluator.getVector(def.startWorldSpeed, 0.0, tmpSpeedAccumulation);
 			evaluator.getVector(def.startWorldSpeed, 0.0, tmpSpeedAccumulation);
-			if(tmpSpeedAccumulation.lengthSq() > 0.001)
-				tmpSpeedAccumulation.transform3x3(emitter.invTransform);
+			tmpSpeedAccumulation.transform3x3(emitter.invTransform);
 			add(speedAccumulation, tmpSpeedAccumulation);
 			add(speedAccumulation, tmpSpeedAccumulation);
 		}
 		}
 
 
 		// ACCELERATION
 		// ACCELERATION
-		evaluator.getVector(def.acceleration, t, tmpSpeedAccumulation);
-		tmpSpeedAccumulation.scale3(dt);
-		if(tmpSpeedAccumulation.lengthSq() > 0.001)
+		if(def.acceleration != VZero) {
+			evaluator.getVector(def.acceleration, t, tmpSpeedAccumulation);
+			tmpSpeedAccumulation.scale3(dt);
 			tmpSpeedAccumulation.transform3x3(orientation.toMatrix(tmpMat));
 			tmpSpeedAccumulation.transform3x3(orientation.toMatrix(tmpMat));
-		add(speedAccumulation, tmpSpeedAccumulation);
+			add(speedAccumulation, tmpSpeedAccumulation);
+		}
+
 		// WORLD ACCELERATION
 		// WORLD ACCELERATION
-		evaluator.getVector(def.worldAcceleration, t, tmpSpeedAccumulation);
-		tmpSpeedAccumulation.scale3(dt);
-		if(tmpSpeedAccumulation.lengthSq() > 0.001 && emitter.simulationSpace == Local)
-			tmpSpeedAccumulation.transform3x3(emitter.invTransform);
-		add(speedAccumulation, tmpSpeedAccumulation);
+		if(def.worldAcceleration != VZero) {
+			evaluator.getVector(def.worldAcceleration, t, tmpSpeedAccumulation);
+			tmpSpeedAccumulation.scale3(dt);
+			if(emitter.simulationSpace == Local)
+				tmpSpeedAccumulation.transform3x3(emitter.invTransform);
+			add(speedAccumulation, tmpSpeedAccumulation);
+		}
+		
+		add(tmpSpeed, speedAccumulation);
+
 		// SPEED
 		// SPEED
-		evaluator.getVector(def.localSpeed, t, tmpLocalSpeed);
-		if(tmpLocalSpeed.lengthSq() > 0.001)
+		if(def.localSpeed != VZero) {
+			evaluator.getVector(def.localSpeed, t, tmpLocalSpeed);
 			tmpLocalSpeed.transform3x3(orientation.toMatrix(tmpMat));
 			tmpLocalSpeed.transform3x3(orientation.toMatrix(tmpMat));
+			add(tmpSpeed, tmpLocalSpeed);
+		}
 		// WORLD SPEED
 		// WORLD SPEED
-		evaluator.getVector(def.worldSpeed, t, tmpWorldSpeed);
-		if(emitter.simulationSpace == Local)
-			tmpWorldSpeed.transform3x3(emitter.invTransform);
-
-		tmpSpeed.set(0,0,0);
-		add(tmpSpeed, tmpLocalSpeed);
-		add(tmpSpeed, tmpWorldSpeed);
-		add(tmpSpeed, speedAccumulation);
+		if(def.worldSpeed != VZero) {
+			evaluator.getVector(def.worldSpeed, t, tmpWorldSpeed);
+			if(emitter.simulationSpace == Local)
+				tmpWorldSpeed.transform3x3(emitter.invTransform);
+			add(tmpSpeed, tmpWorldSpeed);
+		}
 
 
 		if(emitter.simulationSpace == World) {
 		if(emitter.simulationSpace == World) {
 			tmpSpeed.x *= emitter.worldScale.x;
 			tmpSpeed.x *= emitter.worldScale.x;
@@ -284,6 +312,17 @@ private class ParticleInstance  {
 		transform.y += tmpSpeed.y * dt;
 		transform.y += tmpSpeed.y * dt;
 		transform.z += tmpSpeed.z * dt;
 		transform.z += tmpSpeed.z * dt;
 
 
+		if(def.orbitSpeed != VZero) {
+			evaluator.getVector(def.orbitSpeed, t, tmpLocalSpeed);
+			tmpMat.initRotation(tmpLocalSpeed.x * dt, tmpLocalSpeed.y * dt, tmpLocalSpeed.z * dt);
+			// Rotate in emitter space and convert back to world space
+			var pos = transform.getWorldPosition();
+			pos.transform3x4(emitter.getInvPos());
+			pos.transform3x3(tmpMat);
+			pos.transform3x4(emitter.getAbsPos());
+			transform.setWorldPosition(pos);
+		}
+
 		// SPEED ORIENTATION
 		// SPEED ORIENTATION
 		if(emitter.emitOrientation == Speed && tmpSpeed.lengthSq() > 0.01)
 		if(emitter.emitOrientation == Speed && tmpSpeed.lengthSq() > 0.01)
 			transform.qRot.initDirection(tmpSpeed);
 			transform.qRot.initDirection(tmpSpeed);
@@ -296,12 +335,12 @@ private class ParticleInstance  {
 		scaleVec.scale3(evaluator.getFloat(def.scale, t));
 		scaleVec.scale3(evaluator.getFloat(def.scale, t));
 
 
 		// TRANSFORM
 		// TRANSFORM
-		childMat.initScale(scaleVec.x, scaleVec.y, scaleVec.z);
-		childMat.rotate(rot.x, rot.y, rot.z);
-		childMat.translate(offset.x, offset.y, offset.z);
+		localMat.initScale(scaleVec.x, scaleVec.y, scaleVec.z);
+		localMat.rotate(rot.x, rot.y, rot.z);
+		localMat.translate(offset.x, offset.y, offset.z);
 		if( baseMat != null )
 		if( baseMat != null )
-			childMat.multiply(baseMat, childMat);
-		childTransform.setTransform(childMat);
+			localMat.multiply(baseMat, localMat);
+		localTransform.setTransform(localMat);
 
 
 		// COLOR
 		// COLOR
 		if( def.color != null ) {
 		if( def.color != null ) {
@@ -316,14 +355,14 @@ private class ParticleInstance  {
 			case Screen:
 			case Screen:
 				transform.qRot.load(emitter.screenQuat);
 				transform.qRot.load(emitter.screenQuat);
 				transform.calcAbsPos();
 				transform.calcAbsPos();
-				childTransform.calcAbsPos();
-				absPos.multiply(childTransform.absPos, transform.absPos);
+				localTransform.calcAbsPos();
+				absPos.multiply(localTransform.absPos, transform.absPos);
 
 
 			case Axis:
 			case Axis:
 				transform.calcAbsPos();
 				transform.calcAbsPos();
 
 
 				var absChildMat = tmpMat;
 				var absChildMat = tmpMat;
-				absChildMat.multiply3x4(transform.absPos, childMat);
+				absChildMat.multiply3x4(transform.absPos, localMat);
 				tmpCamAlign.load(emitter.alignAxis);
 				tmpCamAlign.load(emitter.alignAxis);
 				tmpCamAlign.transform3x3(absChildMat);
 				tmpCamAlign.transform3x3(absChildMat);
 				tmpCamAlign.normalizeFast();
 				tmpCamAlign.normalizeFast();
@@ -348,17 +387,17 @@ private class ParticleInstance  {
 
 
 				tmpQuat.identity();
 				tmpQuat.identity();
 				tmpQuat.initRotateAxis(emitter.alignLockAxis.x, emitter.alignLockAxis.y, emitter.alignLockAxis.z, angle);
 				tmpQuat.initRotateAxis(emitter.alignLockAxis.x, emitter.alignLockAxis.y, emitter.alignLockAxis.z, angle);
-				var cq = childTransform.qRot;
+				var cq = localTransform.qRot;
 				cq.multiply(cq, tmpQuat);
 				cq.multiply(cq, tmpQuat);
-				childTransform.setRotation(cq);
+				localTransform.setRotation(cq);
 
 
-				childTransform.calcAbsPos();
-				absPos.multiply(childTransform.absPos, transform.absPos);
+				localTransform.calcAbsPos();
+				absPos.multiply(localTransform.absPos, transform.absPos);
 
 
 			case None:
 			case None:
 				transform.calcAbsPos();
 				transform.calcAbsPos();
-				childTransform.calcAbsPos();
-				absPos.multiply(childTransform.absPos, transform.absPos);
+				localTransform.calcAbsPos();
+				absPos.multiply(localTransform.absPos, transform.absPos);
 		}
 		}
 
 
 		// COLLISION
 		// COLLISION
@@ -376,7 +415,7 @@ private class ParticleInstance  {
 					newDir.scale3(speedAmount);
 					newDir.scale3(speedAmount);
 					speedAccumulation.set(newDir.x, newDir.y, newDir.z);
 					speedAccumulation.set(newDir.x, newDir.y, newDir.z);
 					transform.z = 0;
 					transform.z = 0;
-					absPos.multiply(childTransform.absPos, transform.absPos);
+					absPos.multiply(localTransform.absPos, transform.absPos);
 				}
 				}
 			}
 			}
 		}
 		}
@@ -774,8 +813,7 @@ class EmitterObject extends h3d.scene.Object {
 			return;
 			return;
 
 
 		if( parent != null ) {
 		if( parent != null ) {
-			invTransform.load(parent.getAbsPos());
-			invTransform.invert();
+			invTransform.load(parent.getInvPos());
 
 
 			if(alignMode == Screen) {
 			if(alignMode == Screen) {
 				var cam = getScene().camera;
 				var cam = getScene().camera;
@@ -1023,6 +1061,7 @@ class Emitter extends Object3D {
 		{ name: "instWorldSpeed", 			t: PVec(3, -10, 10),  def: [0.,0.,0.], disp: "Fixed World Speed" },
 		{ name: "instWorldSpeed", 			t: PVec(3, -10, 10),  def: [0.,0.,0.], disp: "Fixed World Speed" },
 		{ name: "instStartSpeed",      		t: PVec(3, -10, 10),  def: [0.,0.,0.], disp: "Start Speed" },
 		{ name: "instStartSpeed",      		t: PVec(3, -10, 10),  def: [0.,0.,0.], disp: "Start Speed" },
 		{ name: "instStartWorldSpeed", 		t: PVec(3, -10, 10),  def: [0.,0.,0.], disp: "Start World Speed" },
 		{ name: "instStartWorldSpeed", 		t: PVec(3, -10, 10),  def: [0.,0.,0.], disp: "Start World Speed" },
+		{ name: "instOrbitSpeed", 			t: PVec(3, -10, 10),  def: [0.,0.,0.], disp: "Orbit Speed" },
 		{ name: "instAcceleration",			t: PVec(3, -10, 10),  def: [0.,0.,0.], disp: "Acceleration" },
 		{ name: "instAcceleration",			t: PVec(3, -10, 10),  def: [0.,0.,0.], disp: "Acceleration" },
 		{ name: "instWorldAcceleration",	t: PVec(3, -10, 10),  def: [0.,0.,0.], disp: "World Acceleration" },
 		{ name: "instWorldAcceleration",	t: PVec(3, -10, 10),  def: [0.,0.,0.], disp: "World Acceleration" },
 		{ name: "instScale",      			t: PFloat(0, 2.0),    def: 1.,         disp: "Scale" },
 		{ name: "instScale",      			t: PFloat(0, 2.0),    def: 1.,         disp: "Scale" },
@@ -1145,10 +1184,14 @@ class Emitter extends Object3D {
 				var xCurve = getCurve(pname + suffix);
 				var xCurve = getCurve(pname + suffix);
 				if(xCurve != null)
 				if(xCurve != null)
 					xVal = VCurveScale(xCurve, baseProp != null ? baseProp : 1.0);
 					xVal = VCurveScale(xCurve, baseProp != null ? baseProp : 1.0);
-				else if(baseProp != null)
-					xVal = VConst(baseProp);
-				else
-					xVal = defVal == 0.0 ? VZero : VConst(defVal);
+				else {
+					var rv = baseProp != null ? baseProp : defVal;
+					xVal = switch(rv) {
+						case 0.0: VZero;
+						case 1.0: VOne;
+						default: VConst(rv);
+					}
+				}
 
 
 				var randCurve = getCurve(pname + suffix + ".rand");
 				var randCurve = getCurve(pname + suffix + ".rand");
 				var randVal : Value = VZero;
 				var randVal : Value = VZero;
@@ -1176,10 +1219,16 @@ class Emitter extends Object3D {
 							randProp != null ? (randProp[idx] : Float) : null,
 							randProp != null ? (randProp[idx] : Float) : null,
 							param.name, suffix);
 							param.name, suffix);
 					}
 					}
-					return VVector(
+					var v : Value = VVector(
 						makeComp(0, ".x"),
 						makeComp(0, ".x"),
 						makeComp(1, ".y"),
 						makeComp(1, ".y"),
 						makeComp(2, ".z"));
 						makeComp(2, ".z"));
+					if(v.match(VVector(VZero, VZero, VZero)))
+						v = VZero;
+					else if(v.match(VVector(VOne, VOne, VOne)))
+						v = VOne;
+					return v;
+					
 				default:
 				default:
 					return makeCompVal(baseProp, param.def != null ? param.def : 0.0, randProp, param.name, "");
 					return makeCompVal(baseProp, param.def != null ? param.def : 0.0, randProp, param.name, "");
 			}
 			}
@@ -1198,6 +1247,7 @@ class Emitter extends Object3D {
 				worldSpeed: makeParam(this, "instWorldSpeed"),
 				worldSpeed: makeParam(this, "instWorldSpeed"),
 				startSpeed: makeParam(this, "instStartSpeed"),
 				startSpeed: makeParam(this, "instStartSpeed"),
 				startWorldSpeed: makeParam(this, "instStartWorldSpeed"),
 				startWorldSpeed: makeParam(this, "instStartWorldSpeed"),
+				orbitSpeed: makeParam(this, "instOrbitSpeed"),
 				acceleration: makeParam(this, "instAcceleration"),
 				acceleration: makeParam(this, "instAcceleration"),
 				worldAcceleration: makeParam(this, "instWorldAcceleration"),
 				worldAcceleration: makeParam(this, "instWorldAcceleration"),
 				localOffset: makeParam(this, "instOffset"),
 				localOffset: makeParam(this, "instOffset"),

+ 6 - 0
hrt/prefab/fx/Evaluator.hx

@@ -38,6 +38,7 @@ class Evaluator {
 			return 0.0;
 			return 0.0;
 		switch(val) {
 		switch(val) {
 			case VZero: return 0.0;
 			case VZero: return 0.0;
+			case VOne: return 1.0;
 			case VConst(v): return v;
 			case VConst(v): return v;
 			case VCurve(c): return c.getVal(time);
 			case VCurve(c): return c.getVal(time);
 			case VCurveScale(c, scale): return c.getVal(time) * scale;
 			case VCurveScale(c, scale): return c.getVal(time) * scale;
@@ -59,6 +60,7 @@ class Evaluator {
 
 
 	public function getSum(val: Value, time: Float) : Float {
 	public function getSum(val: Value, time: Float) : Float {
 		switch(val) {
 		switch(val) {
+			case VOne: return time;
 			case VConst(v): return v * time;
 			case VConst(v): return v * time;
 			case VCurveScale(c, scale): return c.getSum(time) * scale;
 			case VCurveScale(c, scale): return c.getSum(time) * scale;
 			case VAdd(a, b):
 			case VAdd(a, b):
@@ -87,6 +89,10 @@ class Evaluator {
 				var aval = getFloat(a, time);
 				var aval = getFloat(a, time);
 				vec.makeColor(hval, sval, lval);
 				vec.makeColor(hval, sval, lval);
 				vec.a = aval;
 				vec.a = aval;
+			case VZero:
+				vec.set(0,0,0,1);
+			case VOne:
+				vec.set(1,1,1,1);
 			default:
 			default:
 				var f = getFloat(v, time);
 				var f = getFloat(v, time);
 				vec.set(f, f, f, 1.0);
 				vec.set(f, f, f, 1.0);

+ 1 - 0
hrt/prefab/fx/Value.hx

@@ -2,6 +2,7 @@ package hrt.prefab.fx;
 
 
 enum Value {
 enum Value {
 	VZero;
 	VZero;
+	VOne;
 	VConst(v: Float);
 	VConst(v: Float);
 	VCurve(c: Curve);
 	VCurve(c: Curve);
 	VCurveScale(c: Curve, scale: Float);
 	VCurveScale(c: Curve, scale: Float);

+ 1 - 23
hrt/prefab/l3d/Level3D.hx

@@ -1,5 +1,6 @@
 package hrt.prefab.l3d;
 package hrt.prefab.l3d;
 
 
+@:deprecated("Use hrt.prefab.Library instead")
 class Level3D extends hrt.prefab.Library {
 class Level3D extends hrt.prefab.Library {
 
 
 	public function new() {
 	public function new() {
@@ -7,28 +8,5 @@ class Level3D extends hrt.prefab.Library {
 		type = "level3d";
 		type = "level3d";
 	}
 	}
 
 
-	#if editor
-
-	override function edit( ctx : EditContext ) {
-		var props = new hide.Element('
-			<div class="group" name="Level">
-					<dl>
-					<dt>Width</dt><dd><input type="number" value="0" field="width"/></dd>
-					<dt>Height</dt><dd><input type="number" value="0" field="height"/></dd>
-					<dt>Grid Size</dt><dd><input type="number" value="0" field="gridSize"/></dd>
-				</dl>
-			</div>
-		');
-		ctx.properties.add(props, this, function(pname) {
-			ctx.onChange(this, pname);
-		});
-	}
-
-	override function getHideProps() : HideProps {
-		return { icon : "cube", name : "Level3D", allowParent: _ -> false};
-	}
-
-	#end
-
 	static var _ = Library.register("level3d", Level3D, "l3d");
 	static var _ = Library.register("level3d", Level3D, "l3d");
 }
 }

+ 16 - 6
hrt/prefab/l3d/Spline.hx

@@ -318,10 +318,18 @@ class Spline extends Object3D {
 		// Linear interpolation between the two samples
 		// Linear interpolation between the two samples
 		else {
 		else {
 			var segmentLength = data.samples[s1].pos.distance(data.samples[s2].pos);
 			var segmentLength = data.samples[s1].pos.distance(data.samples[s2].pos);
-			var t = (l - (s1 * 1./step)) / segmentLength;
-			pos.lerp(data.samples[s1].pos, data.samples[s2].pos, t);
-			if(tangent != null)
-				tangent.lerp(data.samples[s1].tangent, data.samples[s2].tangent, t);
+			if (segmentLength == 0) {
+				pos.load(data.samples[s1].pos);
+				if(tangent != null)
+					tangent.load(data.samples[s1].tangent);
+			}
+			else {
+				var t = (l - (s1 * 1./step)) / segmentLength;
+				pos.lerp(data.samples[s1].pos, data.samples[s2].pos, t);
+				if(tangent != null)
+					tangent.lerp(data.samples[s1].tangent, data.samples[s2].tangent, t);
+			}
+			
 		}
 		}
 		return pos;
 		return pos;
 	}
 	}
@@ -368,10 +376,12 @@ class Spline extends Object3D {
 			var t = 0.;
 			var t = 0.;
 			while (t <= 1.) {
 			while (t <= 1.) {
 				var p = getPointBetween(t, curP, nextP);
 				var p = getPointBetween(t, curP, nextP);
-				samples.insert(samples.length, { pos : p, tangent : getTangentBetween(t, curP, nextP), prev : curP, next : nextP });
+				if (p.distance(samples[samples.length - 1].pos) >= 1./step)
+					samples.insert(samples.length, { pos : p, tangent : getTangentBetween(t, curP, nextP), prev : curP, next : nextP });
 				t += 1./step;
 				t += 1./step;
 			}
 			}
-			samples.insert(samples.length, { pos : nextP.getPoint(), tangent : nextP.getTangent(), prev : curP, next : nextP, t : 1.0 });
+			if (nextP.getPoint().distance(samples[samples.length - 1].pos) >= 1./step)
+				samples.insert(samples.length, { pos : nextP.getPoint(), tangent : nextP.getTangent(), prev : curP, next : nextP, t : 1.0 });
 			curP = points[i];
 			curP = points[i];
 			nextP = points[(i + 1) % points.length];
 			nextP = points[(i + 1) % points.length];