Selaa lähdekoodia

review model viewer (using prefab for render setup)

ncannasse 7 vuotta sitten
vanhempi
commit
965e57bad7
9 muutettua tiedostoa jossa 187 lisäystä ja 147 poistoa
  1. 1 0
      bin/style.css
  2. 1 0
      bin/style.less
  3. 5 1
      hide/Ide.hx
  4. 40 13
      hide/comp/SceneEditor.hx
  5. 23 11
      hide/prefab/Light.hx
  6. 2 1
      hide/view/FXEditor.hx
  7. 99 118
      hide/view/Model.hx
  8. 1 1
      hide/view/Prefab.hx
  9. 15 2
      hide/view/l3d/Level3D.hx

+ 1 - 0
bin/style.css

@@ -376,6 +376,7 @@ input[type=checkbox]:checked:after {
 }
 .hide-block {
   padding: 8px;
+  flex-grow: initial;
 }
 .hide-tabs {
   flex: 0 0 320px;

+ 1 - 0
bin/style.less

@@ -400,6 +400,7 @@ input[type=checkbox] {
 
 .hide-block {
 	padding : 8px;
+	flex-grow: initial;
 }
 
 .hide-tabs {

+ 5 - 1
hide/Ide.hx

@@ -539,6 +539,8 @@ class Ide {
 	}
 
 	public function loadPrefab<T:hide.prefab.Prefab>( file : String, cl : Class<T> ) : T {
+		if( file == null )
+			return null;
 		var l = new hxd.prefab.Library();
 		try {
 			l.load(parseJSON(sys.io.File.getContent(getPath(file))));
@@ -546,12 +548,14 @@ class Ide {
 			error("Invalid prefab ("+e+")");
 			throw e;
 		}
+		if( Std.is(l,cl) )
+			return cast l;
 		return l.get(cl);
 	}
 
 	public function savePrefab( file : String, f : hide.prefab.Prefab ) {
 		var content;
-		if( f.type == "library" )
+		if( Std.is(f,hxd.prefab.Library) )
 			content = f.save();
 		else {
 			var l = new hxd.prefab.Library();

+ 40 - 13
hide/comp/SceneEditor.hx

@@ -75,6 +75,7 @@ class SceneEditor {
 	public var snapToGround = false;
 	public var localTransform = true;
 	public var cameraController : h3d.scene.CameraController;
+	public var editorDisplay(default,set) : Bool;
 
 	var searchBox : Element;
 	var updates : Array<Float -> Void> = [];
@@ -111,6 +112,7 @@ class SceneEditor {
 		scene.onResize = function() {
 			context.shared.root2d.x = scene.width >> 1;
 			context.shared.root2d.y = scene.height >> 1;
+			onResize();
 		};
 
 		context = new hide.prefab.Context();
@@ -118,6 +120,7 @@ class SceneEditor {
 		context.shared = new hide.prefab.ContextShared(scene);
 		context.shared.currentPath = view.state.path;
 		context.init();
+		editorDisplay = true;
 
 		view.keys.register("copy", onCopy);
 		view.keys.register("paste", onPaste);
@@ -143,6 +146,14 @@ class SceneEditor {
 		});
 	}
 
+	public dynamic function onResize() {
+	}
+
+	function set_editorDisplay(v) {
+		context.shared.editorDisplay = v;
+		return editorDisplay = v;
+	}
+
 	public function getSelection() {
 		return curEdit != null ? curEdit.elements : [];
 	}
@@ -298,7 +309,6 @@ class SceneEditor {
 	}
 
 	public function refresh( ?callb ) {
-		var sh = context.shared;
 		refreshScene();
 		tree.refresh(function() {
 			var all = sceneData.flatten(hxd.prefab.Prefab);
@@ -323,6 +333,7 @@ class SceneEditor {
 			if( c != null && c.cleanup != null )
 				c.cleanup();
 		context.shared = sh = new hide.prefab.ContextShared(scene);
+		sh.editorDisplay = editorDisplay;
 		sh.currentPath = view.state.path;
 		scene.s3d.addChild(sh.root3d);
 		scene.s2d.addChild(sh.root2d);
@@ -334,6 +345,10 @@ class SceneEditor {
 		scene.init();
 		scene.engine.backgroundColor = bgcol;
 		refreshInteractives();
+		onRefresh();
+	}
+
+	public dynamic function onRefresh() {
 	}
 
 	function refreshInteractives() {
@@ -354,6 +369,15 @@ class SceneEditor {
 			for(mesh in meshes) {
 				if(mesh.ignoreCollide)
 					continue;
+
+				// invisible objects are ignored collision wise
+				var p : h3d.scene.Object = mesh;
+				while( p != o ) {
+					if( !p.visible ) break;
+					p = p.parent;
+				}
+				if( p != o ) continue;
+
 				var localMat = mesh.getAbsPos().clone();
 				localMat.multiply(localMat, invRootMat);
 				var lb = mesh.primitive.getBounds().clone();
@@ -643,9 +667,9 @@ class SceneEditor {
 		}));
 		refresh(function() {
 			selectObjects([e]);
+			if( e.parent == sceneData && sceneData.children.length == 1 )
+				resetCamera();
 		});
-		if( e.parent == sceneData && sceneData.children.length == 1 )
-			resetCamera();
 	}
 
 	function fillProps( edit, e : PrefabElement ) {
@@ -715,16 +739,11 @@ class SceneEditor {
 		return e == null;
 	}
 
-	public function resetCamera(?top = false) {
-		var targetPt = new h3d.col.Point(0, 0, 0);
-		if(curEdit != null && curEdit.rootObjects.length > 0) {
-			targetPt = curEdit.rootObjects[0].getAbsPos().getPosition().toPoint();
-		}
-		if(top)
-			cameraController.set(200, Math.PI/2, 0.001, targetPt);
-		else
-			cameraController.set(200, -4.7, 0.8, targetPt);
-		cameraController.toTarget();
+	public function resetCamera() {
+		scene.s3d.camera.zNear = scene.s3d.camera.zFar = 0;
+		scene.resetCamera(1.5);
+		cameraController.lockZPlanes = scene.s3d.camera.zNear != 0;
+		cameraController.loadFromCamera();
 	}
 
 	public function getPickTransform(parent: PrefabElement) {
@@ -891,6 +910,10 @@ class SceneEditor {
 		selectObjects([]);
 	}
 
+	public function isSelected( p : PrefabElement ) {
+		return curEdit != null && curEdit.elements.indexOf(p) >= 0;
+	}
+
 	public function setEnabled(elements : Array<PrefabElement>, enable: Bool) {
 		elements = [for(e in elements) if(e.to(Object3D) == null) e]; // Don't disable/enable Object3Ds, too confusing with visibility
 		var old = [for(e in elements) e.enabled];
@@ -1176,6 +1199,10 @@ class SceneEditor {
 		event.update(dt);
 		for( f in updates )
 			f(dt);
+		onUpdate(dt);
+	}
+
+	public dynamic function onUpdate(dt:Float) {
 	}
 
 	// Override

+ 23 - 11
hide/prefab/Light.hx

@@ -87,14 +87,20 @@ class Light extends Object3D {
 		ctx = ctx.clone(this);
 
 		var isPbr = Std.is(h3d.mat.MaterialSetup.current, h3d.mat.PbrMaterialSetup);
-		if( !isPbr )
-			return ctx;
-
-		switch( kind ) {
-		case Point:
-			ctx.local3d = new h3d.scene.pbr.PointLight(ctx.local3d);
-		case Directional:
-			ctx.local3d = new h3d.scene.pbr.DirLight(ctx.local3d);
+		if( !isPbr ) {
+			switch( kind ) {
+			case Point:
+				ctx.local3d = new h3d.scene.PointLight(ctx.local3d);
+			case Directional:
+				ctx.local3d = new h3d.scene.DirLight(ctx.local3d);
+			}
+		} else {
+			switch( kind ) {
+			case Point:
+				ctx.local3d = new h3d.scene.pbr.PointLight(ctx.local3d);
+			case Directional:
+				ctx.local3d = new h3d.scene.pbr.DirLight(ctx.local3d);
+			}
 		}
 		ctx.local3d.name = name;
 		updateInstance(ctx);
@@ -139,9 +145,6 @@ class Light extends Object3D {
 
 		#if editor
 
-		// no "Mixed" in editor (prevent double shadowing)
-		if( light.shadows.mode == Mixed ) light.shadows.mode = Static;
-
 		var debugPoint = ctx.local3d.find(c -> if(c.name == "_debugPoint") c else null);
 		var debugDir = ctx.local3d.find(c -> if(c.name == "_debugDir") c else null);
 		var mesh : h3d.scene.Mesh = null;
@@ -158,15 +161,18 @@ class Light extends Object3D {
 				debugPoint.name = "_debugPoint";
 
 				mesh = new h3d.scene.Mesh(h3d.prim.Sphere.defaultUnitSphere(), debugPoint);
+				mesh.ignoreBounds = true;
 
 				var highlight = new h3d.scene.Object(debugPoint);
 				highlight.name = "_highlight";
 				highlight.visible = false;
 				sizeSphere = new h3d.scene.Sphere(0xffffff, size, true, highlight);
+				sizeSphere.ignoreBounds = true;
 				sizeSphere.ignoreCollide = true;
 				sizeSphere.material.mainPass.setPassName("overlay");
 
 				rangeSphere = new h3d.scene.Sphere(0xffffff, range, true, highlight);
+				rangeSphere.ignoreBounds = true;
 				rangeSphere.name = "selection";
 				rangeSphere.visible = false;
 				rangeSphere.ignoreCollide = true;
@@ -192,12 +198,14 @@ class Light extends Object3D {
 				debugDir.name = "_debugDir";
 
 				mesh = new h3d.scene.Mesh(h3d.prim.Sphere.defaultUnitSphere(), debugDir);
+				mesh.ignoreBounds = true;
 				mesh.scale(0.5);
 
 				var g = new h3d.scene.Graphics(debugDir);
 				g.lineStyle(1, 0xffffff);
 				g.moveTo(0,0,0);
 				g.lineTo(10,0,0);
+				g.ignoreBounds = true;
 				g.ignoreCollide = true;
 				g.visible = false;
 				g.material.mainPass.setPassName("overlay");
@@ -216,7 +224,11 @@ class Light extends Object3D {
 		mat.shadows = false;
 
 		var isSelected = sel.visible;
+		if( debugPoint != null ) debugPoint.visible = isSelected || ctx.shared.editorDisplay;
+		if( debugDir != null ) debugDir.visible = isSelected || ctx.shared.editorDisplay;
 		sel.name = "__selection";
+		// no "Mixed" in editor (prevent double shadowing)
+		if( light.shadows.mode == Mixed ) light.shadows.mode = Static;
 		// when selected, force Dynamic mode (realtime preview)
 		if( isSelected && shadows.mode != None ) light.shadows.mode = Dynamic;
 

+ 2 - 1
hide/view/FXEditor.hx

@@ -295,11 +295,12 @@ class FXEditor extends FileView {
 		axis.lineStyle(1,0x0000FF);
 		axis.moveTo(0,0,0);
 		axis.lineTo(0,0,1);
+		axis.lineStyle();
 		axis.material.mainPass.setPassName("debuggeom");
 		axis.visible = showGrid;
 
 		tools.saveDisplayKey = "FXScene/tools";
-		tools.addButton("video-camera", "Perspective camera", () -> sceneEditor.resetCamera(false));
+		tools.addButton("video-camera", "Perspective camera", () -> sceneEditor.resetCamera());
 
 		function renderProps() {
 			properties.clear();

+ 99 - 118
hide/view/Model.hx

@@ -4,14 +4,13 @@ class Model extends FileView {
 
 	var tools : hide.comp.Toolbar;
 	var obj : h3d.scene.Object;
-	var scene : hide.comp.Scene;
-	var control : h3d.scene.CameraController;
+	var sceneEditor : hide.comp.SceneEditor;
 	var tree : hide.comp.SceneTree;
 	var overlay : Element;
-	var properties : hide.comp.PropsEditor;
-	var light : h3d.scene.DirLight;
-	var lightDirection = new h3d.Vector( 1, 2, -4 );
-	var isPbr : Bool;
+
+	var plight : hxd.prefab.Prefab;
+	var light : h3d.scene.Object;
+	var lightDirection : h3d.Vector;
 
 	var aspeed : hide.comp.Range;
 	var aloop : { function toggle( v : Bool ) : Void; var element : Element; }
@@ -20,6 +19,8 @@ class Model extends FileView {
 	var timecursor : h2d.Bitmap;
 	var currentAnimation : { file : String, name : String };
 	var cameraMove : Void -> Void;
+	var scene(get,never) : hide.comp.Scene;
+	var root : hide.prefab.Prefab;
 
 	override function onDisplay() {
 		element.html('
@@ -31,20 +32,81 @@ class Model extends FileView {
 							<div class="tree"></div>
 						</div>
 					</div>
-					<div class="props">
+					<div class="hide-tabs flex vertical">
+						<div class="hide-block">
+							<table>
+							<tr>
+							<td><input type="button" style="width:145px" value="Export"/>
+							<td><input type="button" style="width:145px" value="Import"/>
+							</tr>
+							</table>
+							<div class="hide-scene-tree hide-list">
+							</div>
+						</div>
+						<div class="props hide-scroll">
+						</div>
 					</div>
 				</div>
 			</div>
 		');
 		tools = new hide.comp.Toolbar(null,element.find(".toolbar"));
 		overlay = element.find(".hide-scene-layer .tree");
-		properties = new hide.comp.PropsEditor(undo, null, element.find(".props"));
-		properties.saveDisplayKey = "Model";
-		scene = new hide.comp.Scene(props, null,element.find(".scene"));
-		scene.onReady = init;
+
+		if( root == null ) {
+			var p = props.get("model.renderProps");
+			if( p != null )
+				root = ide.loadPrefab(p, hxd.prefab.Library);
+		}
+
+		if( root == null ) {
+			var def = new hxd.prefab.Library();
+			new hide.prefab.RenderProps(def).name = "renderer";
+			var l = new hide.prefab.Light(def);
+			l.name = "sunLight";
+			l.kind = Directional;
+			l.power = 1.5;
+			var q = new h3d.Quat();
+			q.initDirection(new h3d.Vector(-1,-1.5,-3));
+			var a = q.toEuler();
+			l.rotationX = Math.round(a.x * 180 / Math.PI);
+			l.rotationY = Math.round(a.y * 180 / Math.PI);
+			l.rotationZ = Math.round(a.z * 180 / Math.PI);
+			l.shadows.mode = Dynamic;
+			l.shadows.size = 1024;
+			root = def;
+		}
+
+		sceneEditor = new hide.comp.SceneEditor(this, root);
+		sceneEditor.editorDisplay = false;
+		sceneEditor.onRefresh = onRefresh;
+		sceneEditor.onUpdate = update;
+
+		element.find(".hide-scene-tree").first().append(sceneEditor.tree.element);
+		element.find(".props").first().append(sceneEditor.properties.element);
+		element.find(".scene").first().append(sceneEditor.scene.element);
+		sceneEditor.tree.element.addClass("small");
+
+		element.find("input[value=Export]").click(function(_) {
+			ide.chooseFileSave("renderer.prefab", function(sel) if( sel != null ) ide.savePrefab(sel, root));
+		});
+		element.find("input[value=Import]").click(function(_) {
+			ide.chooseFile(["prefab"], function(f) {
+				if( f == null ) return;
+				var r = ide.loadPrefab(f, hxd.prefab.Library);
+				if( r == null ) {
+					ide.error("This prefab does not have renderer properties");
+					return;
+				}
+				root = r;
+				rebuild();
+			});
+		});
 	}
 
+	inline function get_scene() return sceneEditor.scene;
+
 	function selectMaterial( m : h3d.mat.Material ) {
+		var properties = sceneEditor.properties;
 		properties.clear();
 
 		properties.add(new Element('
@@ -82,6 +144,7 @@ class Model extends FileView {
 	}
 
 	function selectObject( obj : h3d.scene.Object ) {
+		var properties = sceneEditor.properties;
 		properties.clear();
 
 		var objectCount = 1 + obj.getObjectsCount();
@@ -181,23 +244,23 @@ class Model extends FileView {
 		return out;
 	}
 
-	function init() {
+	function onRefresh() {
 
-		isPbr = Std.is(scene.s3d.renderer, h3d.scene.pbr.Renderer);
+		var r = root.get(hide.prefab.RenderProps);
+		if( r != null ) r.applyProps(scene.s3d.renderer);
+
+		plight = root.getAll(hide.prefab.Light)[0];
+		if( plight != null ) {
+			this.light = sceneEditor.context.shared.contexts.get(plight).local3d;
+			lightDirection = this.light.getDirection();
+		}
 
 		undo.onChange = function() {};
 
 		obj = scene.loadModel(state.path, true);
 		new h3d.scene.Object(scene.s3d).addChild(obj);
 
-		light = obj.find(function(o) return Std.instance(o, h3d.scene.DirLight));
-		if( light == null ) {
-			light = new h3d.scene.DirLight(scene.s3d);
-			light.enableSpecular = true;
-			if( isPbr ) light.color.scale3(4);
-		}
-
-		control = new h3d.scene.CameraController(scene.s3d);
+		if( tree != null ) tree.remove();
 		tree = new hide.comp.SceneTree(obj, overlay, obj.name != null);
 		tree.onSelectMaterial = selectMaterial;
 		tree.onSelectObject = selectObject;
@@ -205,16 +268,7 @@ class Model extends FileView {
 		this.saveDisplayKey = "Model:" + state.path;
 		tree.saveDisplayKey = this.saveDisplayKey;
 
-		var cam = getDisplayState("Camera");
-		scene.s3d.camera.zNear = scene.s3d.camera.zFar = 0;
-		scene.resetCamera(obj, 1.5);
-		control.lockZPlanes = scene.s3d.camera.zNear != 0;
-		if( cam != null ) {
-			scene.s3d.camera.pos.set(cam.x, cam.y, cam.z);
-			scene.s3d.camera.target.set(cam.tx, cam.ty, cam.tz);
-		}
-		control.loadFromCamera();
-
+		tools.element.empty();
 		var anims = scene.listAnims(getPath());
 		if( anims.length > 0 ) {
 			var sel = tools.addSelect("play-circle");
@@ -230,84 +284,9 @@ class Model extends FileView {
 		tools.saveDisplayKey = "ModelTools";
 
 		tools.addButton("video-camera", "Reset Camera", function() {
-			scene.resetCamera(obj,1.5);
-			control.loadFromCamera();
+			sceneEditor.resetCamera();
 		});
 
-		function renderProps() {
-			properties.clear();
-
-			var renderer = scene.s3d.renderer;
-
-			var e = properties.add(new Element('<table>
-			<tr>
-			<td><input type="button" style="width:145px" value="Export"/>
-			<td><input type="button" style="width:145px" value="Import"/>
-			</tr>
-			</table>'));
-			e.find("input[value=Export]").click(function(_) {
-				ide.chooseFileSave("renderer.prefab", function(sel) {
-					var r = new hide.prefab.RenderProps();
-					r.name = h3d.mat.MaterialSetup.current.name;
-					r.setProps(renderer.props);
-					ide.savePrefab(sel, r);
-				});
-			});
-			e.find("input[value=Import]").click(function(_) {
-				ide.chooseFile(["prefab"], function(f) {
-					var r = ide.loadPrefab(f, hide.prefab.RenderProps);
-					if( r == null ) {
-						ide.error("This prefab does not have renderer properties");
-						return;
-					}
-					if( !r.applyProps(renderer) )
-						ide.error("This prefab does not contain "+h3d.mat.MaterialSetup.current.name+" renderer properties");
-				});
-			});
-
-			var group = renderer.editProps();
-			properties.add(group, renderer.props, function(_) {
-				renderer.refreshProps();
-				if( !properties.isTempChange ) renderProps();
-			});
-
-			var lprops = {
-				power : Math.sqrt(light.color.r),
-				enable: true
-			};
-			var group = new Element('
-			<div class="group" name="Light">
-				<dl>
-				<dt>Power</dt><dd><input type="range" min="0" max="4" field="power"/></dd>
-				</dl>
-			</div>
-			');
-			if(!isPbr) {
-				var enable = new Element('<dt>Enable</dt><dd><input type="checkbox" field="enable"/></dd>');
-				group.find('dl').append(enable);
-			}
-			properties.add(group, lprops, function(_) {
-				var p = lprops.power * lprops.power;
-				light.color.set(p, p, p);
-				if(!isPbr) {
-					if(!lprops.enable) {
-						for( m in obj.getMaterials() ) {
-							m.mainPass.enableLights = false;
-							m.shadows = false;
-						}
-					}
-					else {
-						for( m in obj.getMaterials() ) {
-							var props = h3d.mat.MaterialSetup.current.loadMaterialProps(m);
-							if( props == null ) props = m.getDefaultModelProps();
-							m.props = props;
-						}
-					}
-				}
-			});
-		}
-		tools.addButton("gears", "Renderer Properties", renderProps);
-
 		var axis = new h3d.scene.Graphics(scene.s3d);
 		axis.lineStyle(1,0xFF0000);
 		axis.lineTo(1,0,0);
@@ -317,11 +296,9 @@ class Model extends FileView {
 		axis.lineStyle(1,0x0000FF);
 		axis.moveTo(0,0,0);
 		axis.lineTo(0,0,1);
+		axis.lineStyle();
 		axis.visible = false;
 
-		scene.init();
-		scene.onUpdate = update;
-
 		tools.addToggle("location-arrow", "Toggle Axis", function(v) {
 			axis.visible = v;
 		});
@@ -349,7 +326,7 @@ class Model extends FileView {
 
 		initConsole();
 
-		scene.onResize = buildTimeline;
+		sceneEditor.onResize = buildTimeline;
 		setAnimation(null);
 	}
 
@@ -367,7 +344,7 @@ class Model extends FileView {
 					Math.cos(angle) * ray + cam.target.x,
 					Math.sin(angle) * ray + cam.target.y,
 					cam.pos.z);
-				control.loadFromCamera();
+				sceneEditor.cameraController.loadFromCamera();
 			};
 		});
 		c.addCommand("stop", [], function() {
@@ -488,12 +465,16 @@ class Model extends FileView {
 		var cam = scene.s3d.camera;
 		saveDisplayState("Camera", { x : cam.pos.x, y : cam.pos.y, z : cam.pos.z, tx : cam.target.x, ty : cam.target.y, tz : cam.target.z });
 		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
-			));
+			if( sceneEditor.isSelected(plight) )
+				lightDirection = light.getDirection();
+			else {
+				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
+				));
+			}
 		}
 		if( timeline != null ) {
 			timecursor.x = Std.int((obj.currentAnimation.frame / obj.currentAnimation.frameCount) * (scene.s2d.width - timecursor.tile.width));

+ 1 - 1
hide/view/Prefab.hx

@@ -104,7 +104,7 @@ class Prefab extends FileView {
 			light = null;
 
 		tools.saveDisplayKey = "Prefab/tools";
-		tools.addButton("video-camera", "Perspective camera", () -> sceneEditor.resetCamera(false));
+		tools.addButton("video-camera", "Perspective camera", () -> sceneEditor.resetCamera());
 
 		tools.addColor("Background color", function(v) {
 			scene.engine.backgroundColor = v;

+ 15 - 2
hide/view/l3d/Level3D.hx

@@ -301,8 +301,8 @@ class Level3D extends FileView {
 	public function onSceneReady() {
 
 		tools.saveDisplayKey = "Level3D/toolbar";
-		tools.addButton("video-camera", "Perspective camera", () -> sceneEditor.resetCamera(false));
-		tools.addButton("video-camera", "Top camera", () -> sceneEditor.resetCamera(true)).find(".icon").css({transform: "rotateZ(90deg)"});
+		tools.addButton("video-camera", "Perspective camera", () -> resetCamera(false));
+		tools.addButton("video-camera", "Top camera", () -> resetCamera(true)).find(".icon").css({transform: "rotateZ(90deg)"});
 		tools.addToggle("anchor", "Snap to ground", (v) -> sceneEditor.snapToGround = v, sceneEditor.snapToGround);
 		tools.addToggle("compass", "Local transforms", (v) -> sceneEditor.localTransform = v, sceneEditor.localTransform);
 		tools.addToggle("th", "Show grid", function(v) { showGrid = v; updateGrid(); }, showGrid);
@@ -361,6 +361,19 @@ class Level3D extends FileView {
 		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 hide.prefab.l3d.Level3D().save()));
 	}