Browse Source

prefab system

ncannasse 7 years ago
parent
commit
da094c87fb

+ 1 - 1
hide.hxml

@@ -9,5 +9,5 @@
 -D hscriptPos
 -D old-error-format
 -D multidriver
--D hide
+-D editor
 -dce no

+ 1 - 1
hide.hxproj

@@ -24,7 +24,7 @@
     <option noInlineOnDebug="False" />
     <option mainClass="hide.ui.Ide" />
     <option enabledebug="False" />
-    <option additional="-D hscriptPos&#xA;-D old-error-format&#xA;-D multidriver&#xA;-D hide&#xA;-dce no" />
+    <option additional="-D hscriptPos&#xA;-D old-error-format&#xA;-D multidriver&#xA;-D editor&#xA;-dce no" />
   </build>
   <!-- haxelib libraries -->
   <haxelib>

+ 54 - 0
hide/prefab/Constraint.hx

@@ -0,0 +1,54 @@
+package hide.prefab;
+
+class Constraint extends Prefab {
+
+	var object : String;
+	var target : String;
+
+	override public function load(v:Dynamic) {
+		object = v.object;
+		target = v.target;
+	}
+
+	override function save() {
+		return { object : object, target : target };
+	}
+
+	override function makeInstance( ctx : Context ) {
+		var srcObj = ctx.locateObject(object);
+		var targetObj = ctx.locateObject(target);
+		if( srcObj != null ) srcObj.follow = targetObj;
+		return ctx;
+	}
+
+	override function getHideProps() : HideProps {
+		return { icon : "lock", name : "Constraint" };
+	}
+
+	override function edit(ctx:EditContext) {
+		#if editor
+		var curObj = ctx.rootContext.locateObject(object);
+		var props = ctx.properties.add(new hide.Element('
+			<dl>
+				<dt>Source</dt><dd><select field="object"><option value="">-- Choose --</option></select>
+				<dt>Target</dt><dd><select field="target"><option value="">-- Choose --</option></select>
+			</dl>
+		'),this, function(_) {
+			if( curObj != null ) curObj.follow = null;
+			makeInstance(ctx.rootContext);
+			curObj = ctx.rootContext.locateObject(object);
+		});
+		for( select in [props.find("[field=object]"), props.find("[field=target]")] ) {
+			for( path in ctx.getNamedObjects() ) {
+				var parts = path.split(".");
+				var opt = new hide.Element("<option>").attr("value", path).html([for( p in 1...parts.length ) "&nbsp; "].join("") + parts.pop());
+				select.append(opt);
+			}
+			select.val(Reflect.field(this, select.attr("field")));
+		}
+		#end
+	}
+
+	static var _ = Library.register("constraint", Constraint);
+
+}

+ 83 - 0
hide/prefab/Context.hx

@@ -0,0 +1,83 @@
+package hide.prefab;
+
+class ContextShared {
+	public var root2d : h2d.Sprite;
+	public var root3d : h3d.scene.Object;
+	public var contexts : Map<Prefab,Context>;
+	var cache : h3d.prim.ModelCache;
+
+	public function new() {
+		root2d = new h2d.Sprite();
+		root3d = new h3d.scene.Object();
+		contexts = new Map();
+		cache = new h3d.prim.ModelCache();
+	}
+
+}
+
+class Context {
+
+	public var local2d : h2d.Sprite;
+	public var local3d : h3d.scene.Object;
+	public var shared : ContextShared;
+
+	public function new() {
+	}
+
+	public function init() {
+		if( shared == null )
+			shared = new ContextShared();
+		local2d = shared.root2d;
+		local3d = shared.root3d;
+	}
+
+	public function clone( p : Prefab ) {
+		var c = new Context();
+		c.shared = shared;
+		c.local2d = local2d;
+		c.local3d = local3d;
+		if( p != null ) shared.contexts.set(p, c);
+		return c;
+	}
+
+	public dynamic function onError( e : Dynamic ) {
+		throw e;
+	}
+
+	public function loadModel( path : String ) {
+		return @:privateAccess shared.cache.loadModel(hxd.res.Loader.currentInstance.load(path).toModel());
+	}
+
+	public function loadAnimation( path : String ) {
+		return @:privateAccess shared.cache.loadAnimation(hxd.res.Loader.currentInstance.load(path).toModel());
+	}
+
+	public function locateObject( path : String ) {
+		if( path == null )
+			return null;
+		var parts = path.split(".");
+		var root = shared.root3d;
+		while( parts.length > 0 ) {
+			var v = null;
+			var pname = parts.shift();
+			for( o in root )
+				if( o.name == pname ) {
+					v = o;
+					break;
+				}
+			if( v == null ) {
+				v = root.getObjectByName(pname);
+				if( v != null && v.parent != root ) v = null;
+			}
+			if( v == null ) {
+				var parts2 = path.split(".");
+				for( i in 0...parts.length ) parts2.pop();
+				onError("Object not found " + parts2.join("."));
+				return null;
+			}
+			root = v;
+		}
+		return root;
+	}
+
+}

+ 58 - 0
hide/prefab/EditContext.hx

@@ -0,0 +1,58 @@
+package hide.prefab;
+
+class EditContext {
+
+	public var rootContext : Context;
+
+	#if editor
+	public var prefabPath : String;
+	public var ide(get,never) : hide.ui.Ide;
+	public var scene : hide.comp.Scene;
+	public var properties : hide.comp.PropsEditor;
+	function get_ide() return hide.ui.Ide.inst;
+	#end
+
+	public function new(ctx) {
+		this.rootContext = ctx;
+	}
+
+	public function getContext( p : Prefab ) {
+		return rootContext.shared.contexts.get(p);
+	}
+
+	public function refresh() {
+	}
+
+	public function getNamedObjects( ?exclude : h3d.scene.Object ) {
+		var out = [];
+
+		function getJoint(path:Array<String>,j:h3d.anim.Skin.Joint) {
+			path.push(j.name);
+			out.push(path.join("."));
+			for( j in j.subs )
+				getJoint(path, j);
+			path.pop();
+		}
+
+		function getRec(path:Array<String>, o:h3d.scene.Object) {
+			if( o == exclude || o.name == null ) return;
+			path.push(o.name);
+			out.push(path.join("."));
+			for( c in o )
+				getRec(path, c);
+			var sk = Std.instance(o, h3d.scene.Skin);
+			if( sk != null ) {
+				var j = sk.getSkinData();
+				for( j in j.rootJoints )
+					getJoint(path, j);
+			}
+			path.pop();
+		}
+
+		for( o in rootContext.shared.root3d )
+			getRec([], o);
+
+		return out;
+	}
+
+}

+ 7 - 0
hide/prefab/HideProps.hx

@@ -0,0 +1,7 @@
+package hide.prefab;
+
+typedef HideProps = {
+	var icon : String;
+	var name : String;
+	@:optional var fileSource : Array<String>;
+}

+ 45 - 0
hide/prefab/Library.hx

@@ -0,0 +1,45 @@
+package hide.prefab;
+
+class Library extends Prefab {
+
+	var inRec = false;
+
+	public function new() {
+		super(null);
+		type = "prefab";
+	}
+
+	// hacks to use directly non-recursive api
+
+	override function load( obj : Dynamic ) {
+		var children : Array<Dynamic> = obj.children;
+		if( children != null )
+			for( v in children )
+				Prefab.loadRec(v, this);
+	}
+
+	override function save() {
+		if( inRec )
+			return {};
+		inRec = true;
+		var obj = saveRec();
+		inRec = false;
+		return obj;
+	}
+
+	override function makeInstance(ctx:Context):Context {
+		if( inRec )
+			return ctx;
+		inRec = true;
+		makeInstanceRec(ctx);
+		inRec = false;
+		return ctx;
+	}
+
+	static var registeredElements = new Map<String,Class<Prefab>>();
+	public static function register( type : String, cl : Class<Prefab> ) {
+		registeredElements.set(type, cl);
+		return true;
+	}
+
+}

+ 97 - 0
hide/prefab/Model.hx

@@ -0,0 +1,97 @@
+package hide.prefab;
+
+class Model extends Object3D {
+
+	var animation : Null<String>;
+	var lockAnimation : Bool = false;
+
+	override function save() {
+		var obj : Dynamic = super.save();
+		if( animation != null ) obj.animation = animation;
+		if( lockAnimation ) obj.lockAnimation = lockAnimation;
+		return obj;
+	}
+
+	override function load( obj : Dynamic ) {
+		super.load(obj);
+		animation = obj.animation;
+		lockAnimation = obj.lockAnimation;
+	}
+
+	override function makeInstance(ctx:Context):Context {
+		ctx = ctx.clone(this);
+		if( source != null ) {
+			try {
+				var obj = ctx.loadModel(source);
+				obj.name = name;
+				applyPos(obj);
+				ctx.local3d.addChild(obj);
+				ctx.local3d = obj;
+
+				if( animation != null )
+					obj.playAnimation(ctx.loadAnimation(animation));
+
+				return ctx;
+			} catch( e : hxd.res.NotFound ) {
+				ctx.onError(e);
+			}
+		}
+		ctx.local3d = new h3d.scene.Object(ctx.local3d);
+		ctx.local3d.name = name;
+		applyPos(ctx.local3d);
+		return ctx;
+	}
+
+	override function edit( ctx : EditContext ) {
+		super.edit(ctx);
+		#if editor
+		var props = ctx.properties.add(new hide.Element('
+			<div class="group" name="Animation">
+				<dl>
+					<dt>Animation</dt><dd><select><option value="">-- Choose --</option></select>
+					<dt title="Don\'t save animation changes">Lock</dt><dd><input type="checkbox" field="lockAnimation"></select>
+				</dl>
+			</div>
+		'),this);
+
+		var select = props.find("select");
+		var anims = ctx.scene.listAnims(source);
+		for( a in anims )
+			new hide.Element('<option>').attr("value", ctx.ide.makeRelative(a)).text(ctx.scene.animationName(a)).appendTo(select);
+		if( animation != null )
+			select.val(animation);
+		select.change(function(_) {
+			var v = select.val();
+			var prev = animation;
+			var obj = ctx.getContext(this).local3d;
+			if( v == "" ) {
+				animation = null;
+				obj.stopAnimation();
+			} else {
+				obj.playAnimation(ctx.rootContext.loadAnimation(v)).loop = true;
+				if( lockAnimation ) return;
+				animation = v;
+			}
+			var newValue = animation;
+			ctx.properties.undo.change(Custom(function(undo) {
+				var obj = ctx.getContext(this).local3d;
+				animation = undo ? prev : newValue;
+				if( animation == null ) {
+					obj.stopAnimation();
+					select.val("");
+				} else {
+					obj.playAnimation(ctx.rootContext.loadAnimation(animation)).loop = true;
+					select.val(v);
+				}
+			}));
+		});
+		#end
+	}
+
+	override function getHideProps() {
+		return { icon : "cube", name : "Model", fileSource : ["fbx","hmd"] };
+	}
+
+	static var _ = Library.register("model", Model);
+
+}

+ 79 - 0
hide/prefab/Object3D.hx

@@ -0,0 +1,79 @@
+package hide.prefab;
+
+class Object3D extends Prefab {
+
+	public var x : Float = 0.;
+	public var y : Float = 0.;
+	public var z : Float = 0.;
+	public var scaleX : Float = 1.;
+	public var scaleY : Float = 1.;
+	public var scaleZ : Float = 1.;
+	public var rotationX : Float = 0.;
+	public var rotationY : Float = 0.;
+	public var rotationZ : Float = 0.;
+	public var visible : Bool = true;
+
+	override function load( obj : Dynamic ) {
+		x = obj.x == null ? 0. : obj.x;
+		y = obj.y == null ? 0. : obj.y;
+		z = obj.z == null ? 0. : obj.z;
+
+		scaleX = obj.scaleX == null ? 1. : obj.scaleX;
+		scaleY = obj.scaleY == null ? 1. : obj.scaleY;
+		scaleZ = obj.scaleZ == null ? 1. : obj.scaleZ;
+
+		rotationX = obj.rotationX == null ? 0. : obj.rotationX;
+		rotationY = obj.rotationY == null ? 0. : obj.rotationY;
+		rotationZ = obj.rotationZ == null ? 0. : obj.rotationZ;
+
+		visible = obj.visible == null ? true : obj.visible;
+	}
+
+	override function save() {
+		var o : Dynamic = {};
+		if( x != 0 ) o.x = x;
+		if( y != 0 ) o.y = y;
+		if( z != 0 ) o.z = z;
+		if( scaleX != 1 ) o.scaleX = scaleX;
+		if( scaleY != 1 ) o.scaleY = scaleY;
+		if( scaleZ != 1 ) o.scaleZ = scaleZ;
+		if( rotationX != 0 ) o.rotationX = rotationX;
+		if( rotationY != 0 ) o.rotationY = rotationY;
+		if( rotationZ != 0 ) o.rotationZ = rotationZ;
+		if( !visible ) o.visible = visible;
+		return o;
+	}
+
+	function applyPos( o : h3d.scene.Object ) {
+		o.x = x;
+		o.y = y;
+		o.z = z;
+		o.scaleX = scaleX;
+		o.scaleY = scaleY;
+		o.scaleZ = scaleZ;
+		o.setRotate(rotationX, rotationY, rotationZ);
+		o.visible = visible;
+	}
+
+	override function edit( ctx : EditContext ) {
+		#if editor
+		ctx.properties.add(new hide.Element('
+			<div class="group" name="Position">
+				<dl>
+					<dt>X</dt><dd><input type="range" min="-10" max="10" field="x"/></dd>
+					<dt>Y</dt><dd><input type="range" min="-10" max="10" field="y"/></dd>
+					<dt>Z</dt><dd><input type="range" min="-10" max="10" field="z"/></dd>
+					<dt>ScaleX</dt><dd><input type="range" min="0" max="5" field="scaleX"/></dd>
+					<dt>ScaleY</dt><dd><input type="range" min="0" max="5" field="scaleY"/></dd>
+					<dt>ScaleZ</dt><dd><input type="range" min="0" max="5" field="scaleZ"/></dd>
+					<dt>RotationX</dt><dd><input type="range" min="-180" max="180" field="rotationX" scale="${Math.PI/180}"/></dd>
+					<dt>RotationY</dt><dd><input type="range" min="-180" max="180" field="rotationY" scale="${Math.PI/180}"/></dd>
+					<dt>RotationZ</dt><dd><input type="range" min="-180" max="180" field="rotationZ" scale="${Math.PI/180}"/></dd>
+					<dt>Visible</dt><dd><input type="checkbox" field="visible"/></dd>
+				</dl>
+			</div>
+		'),this,function(_) applyPos(ctx.getContext(this).local3d));
+		#end
+	}
+
+}

+ 118 - 0
hide/prefab/Prefab.hx

@@ -0,0 +1,118 @@
+package hide.prefab;
+
+class Prefab {
+
+	public var type(default, null) : String;
+	public var name(default, set) : String;
+	public var parent(default, set) : Prefab;
+	public var source(default, set) : String;
+	public var children(default, null) : Array<Prefab>;
+
+	public function new(?parent) {
+		this.parent = parent;
+		children = [];
+	}
+
+	function set_name(n) {
+		return name = n;
+	}
+
+	function set_source(f) {
+		return source = f;
+	}
+
+	function set_parent(p) {
+		if( parent != null )
+			parent.children.remove(this);
+		parent = p;
+		if( parent != null )
+			parent.children.push(this);
+		return p;
+	}
+
+	public function edit( ctx : EditContext ) {
+	}
+
+	public function getHideProps() : HideProps {
+		return { icon : "question-circle", name : "Unknown" };
+	}
+
+	public inline function iterator() : Iterator<Prefab> {
+		return children.iterator();
+	}
+
+	public function load( v : Dynamic ) {
+		throw "Not implemented";
+	}
+
+	public function save() : {} {
+		throw "Not implemented";
+		return null;
+	}
+
+	public function makeInstance( ctx : Context ) : Context {
+		throw "Not implemented";
+		return null;
+	}
+
+	public function saveRec() : {} {
+		var obj : Dynamic = save();
+		obj.type = type;
+		if( name != null )
+			obj.name = name;
+		if( source != null )
+			obj.source = source;
+		if( children.length > 0 )
+			obj.children = [for( s in children ) s.saveRec()];
+		return obj;
+	}
+
+	public static function loadRec( v : Dynamic, ?parent : Prefab ) {
+		var pcl = @:privateAccess Library.registeredElements.get(v.type);
+		if( pcl == null ) throw "Unregistered prefab " + v.type;
+		var p = Type.createInstance(pcl, [parent]);
+		p.type = v.type;
+		p.name = v.name;
+		if( v.source != null )
+			p.source = v.source;
+		p.load(v);
+		var children : Array<Dynamic> = p.children;
+		if( children != null )
+			for( v in children )
+				loadRec(v, p);
+		return p;
+	}
+
+	public function makeInstanceRec( ctx : Context ) {
+		if( ctx == null ) {
+			ctx = new Context();
+			ctx.init();
+		}
+		ctx = makeInstance(ctx);
+		for( c in children )
+			c.makeInstanceRec(ctx);
+	}
+
+	public function getPrefabByName( name : String ) {
+		if( this.name == name )
+			return this;
+		for( c in children ) {
+			var p = c.getPrefabByName(name);
+			if( p != null )
+				return p;
+		}
+		return null;
+	}
+
+	public function get<T:Prefab>( cl : Class<T>, ?name : String ) : T {
+		for( c in children ) {
+			if( (name == null || c.name == name) && Std.is(c, cl) )
+				return cast c;
+			var p = c.get(cl, name);
+			if( p != null )
+				return p;
+		}
+		return null;
+	}
+
+}

+ 69 - 0
hide/prefab/Trail.hx

@@ -0,0 +1,69 @@
+package hide.prefab;
+
+class Trail extends Object3D {
+
+	var data : Dynamic;
+
+	function new(?parent) {
+		super(parent);
+		data = new h3d.scene.Trail().save();
+	}
+
+	override function load(obj:Dynamic) {
+		super.load(obj);
+		data = obj.data;
+	}
+
+	override function save() : {} {
+		var obj : Dynamic = super.save();
+		obj.data = data;
+		return obj;
+	}
+
+	override function makeInstance(ctx:Context):Context {
+		ctx = ctx.clone(this);
+		var tr = new h3d.scene.Trail(ctx.local3d);
+		tr.load(data);
+		applyPos(tr);
+		tr.name = name;
+		ctx.local3d = tr;
+		return ctx;
+	}
+
+	override function getHideProps():HideProps {
+		return { icon : "toggle-on", name : "Trail" };
+	}
+
+
+	override public function edit(ctx:EditContext) {
+		#if editor
+		super.edit(ctx);
+
+		var trail = Std.instance(ctx.getContext(this).local3d, h3d.scene.Trail);
+		var props = ctx.properties.add(new hide.Element('
+		<div class="group" name="Material">
+		</div>
+		<div class="group" name="Trail Properties">
+			<dl>
+				<dt>Angle</dt><dd><input type="range" field="angle" scale="${180/Math.PI}" min="0" max="${Math.PI*2}"/></dd>
+				<dt>Duration</dt><dd><input type="range" field="duration" min="0" max="1"/></dd>
+				<dt>Size Start</dt><dd><input type="range" field="sizeStart" min="0" max="10"/></dd>
+				<dt>Size End</dt><dd><input type="range" field="sizeEnd" min="0" max="10"/></dd>
+				<dt>Movement Min.</dt><dd><input type="range" field="movementMin" min="0" max="1"/></dd>
+				<dt>Movement Max.</dt><dd><input type="range" field="movementMax" min="0" max="1"/></dd>
+				<dt>Smoothness</dt><dd><input type="range" field="smoothness" min="0" max="1"/></dd>
+				<dt>Texture</dt><dd><input type="texture" field="texture"/></dd>
+			</dl>
+		</div>
+		'),trail, function(_) {
+			data = trail.save();
+		});
+		ctx.properties.addMaterial( trail.material, props.find("[name=Material] > .content"), function(_) data = trail.save());
+		#end
+	}
+
+
+
+	static var _ = Library.register("trail", Trail);
+
+}

+ 1 - 1
hide/ui/Ide.hx

@@ -528,7 +528,7 @@ class Ide {
 	public static var inst : Ide;
 
 	static function main() {
-		hide.tools.Macros.include(["hide.view","h3d.prim","h3d.scene","h3d.pass","hxd.prefab"]);
+		hide.tools.Macros.include(["hide.view","h3d.prim","h3d.scene","h3d.pass","hide.prefab"]);
 		new Ide();
 	}
 

+ 269 - 0
hide/view/Prefab.hx

@@ -0,0 +1,269 @@
+package hide.view;
+
+import hide.prefab.Prefab in PrefabElement;
+
+class Prefab extends FileView {
+
+	var data : hide.prefab.Library;
+	var context : hide.prefab.Context;
+	var tabs : hide.comp.Tabs;
+
+	var tools : hide.comp.Toolbar;
+	var scene : hide.comp.Scene;
+	var control : h3d.scene.CameraController;
+	var properties : hide.comp.PropsEditor;
+	var light : h3d.scene.DirLight;
+	var lightDirection = new h3d.Vector( 1, 2, -4 );
+	var tree : hide.comp.IconTree<PrefabElement>;
+
+	override function getDefaultContent() {
+		return haxe.io.Bytes.ofString(ide.toJSON(new hide.prefab.Library().save()));
+	}
+
+	override function save() {
+		sys.io.File.saveContent(getPath(), ide.toJSON(data.save()));
+	}
+
+	override function onDisplay() {
+
+		root.html('
+			<div class="flex vertical">
+				<div class="toolbar"></div>
+				<div class="flex">
+					<div class="scene">
+					</div>
+					<div class="tabs">
+						<div class="tab" name="Scene" icon="sitemap">
+							<div class="hide-block">
+								<div class="hide-list">
+									<div class="tree"></div>
+								</div>
+							</div>
+							<div class="props"></div>
+						</div>
+					</div>
+				</div>
+			</div>
+		');
+		tools = new hide.comp.Toolbar(root.find(".toolbar"));
+		tabs = new hide.comp.Tabs(root.find(".tabs"));
+		properties = new hide.comp.PropsEditor(root.find(".props"), undo);
+		scene = new hide.comp.Scene(root.find(".scene"));
+		scene.onReady = init;
+		tree = new hide.comp.IconTree(root.find(".tree"));
+	}
+
+	function refresh( ?callb ) {
+		var sh = context.shared;
+		sh.root2d.remove();
+		sh.root3d.remove();
+		sh.root2d = new h2d.Sprite();
+		sh.root3d = new h3d.scene.Object();
+		context.init();
+		data.makeInstance(context);
+		scene.s2d.addChild(sh.root2d);
+		scene.s3d.addChild(sh.root3d);
+		scene.init(props);
+		tree.refresh(callb);
+	}
+
+	function allocName( prefix : String ) {
+		var id = 0;
+		while( data.getPrefabByName(prefix + id) != null )
+			id++;
+		return prefix + id;
+	}
+
+	function selectObject( elt : PrefabElement ) {
+		properties.clear();
+		var edit = new hide.prefab.EditContext(context);
+		edit.prefabPath = state.path;
+		edit.properties = properties;
+		edit.scene = scene;
+		elt.edit(edit);
+	}
+
+	function resetCamera() {
+		var bounds = context.shared.root2d.getBounds();
+		context.shared.root2d.x = Std.int(bounds.width) >> 1;
+		context.shared.root2d.y = Std.int(bounds.height) >> 1;
+		scene.resetCamera(context.shared.root3d, 1.5);
+		control.loadFromCamera();
+	}
+
+	function addObject( e : PrefabElement ) {
+		var roots = e.parent.children;
+		undo.change(Custom(function(undo) {
+			if( undo )
+				roots.remove(e);
+			else
+				roots.push(e);
+			refresh();
+		}));
+		refresh(function() {
+			tree.setSelection([e]);
+			selectObject(e);
+		});
+		if( e.parent == data && data.children.length == 1 )
+			resetCamera();
+	}
+
+	function init() {
+		data = new hide.prefab.Library();
+		data.load(haxe.Json.parse(sys.io.File.getContent(getPath())));
+
+		context = new hide.prefab.Context();
+		context.onError = function(e) {
+			ide.error(e);
+		};
+		context.init();
+		scene.s2d.addChild(context.shared.root2d);
+		scene.s3d.addChild(context.shared.root3d);
+
+		data.makeInstance(context);
+
+		light = scene.s3d.find(function(o) return Std.instance(o, h3d.scene.DirLight));
+		if( light == null ) {
+			light = new h3d.scene.DirLight(new h3d.Vector(), scene.s3d);
+			light.enableSpecular = true;
+		} else
+			light = null;
+
+		control = new h3d.scene.CameraController(scene.s3d);
+
+		this.saveDisplayKey = "Scene:" + state.path;
+
+		var cam = getDisplayState("Camera");
+		if( cam == null )
+			resetCamera();
+		else {
+			scene.s3d.camera.pos.set(cam.x, cam.y, cam.z);
+			scene.s3d.camera.target.set(cam.tx, cam.ty, cam.tz);
+		}
+		control.loadFromCamera();
+
+		scene.onUpdate = update;
+		scene.init(props);
+		tools.saveDisplayKey = "SceneTools";
+
+		tools.addButton("video-camera", "Reset Camera", resetCamera);
+		tools.addToggle("sun-o", "Enable Lights/Shadows", function(v) {
+			if( !v ) {
+				for( m in context.shared.root3d.getMaterials() ) {
+					m.mainPass.enableLights = false;
+					m.shadows = false;
+				}
+			} else {
+				for( m in context.shared.root3d.getMaterials() )
+					h3d.mat.MaterialSetup.current.initModelMaterial(m);
+			}
+		},true);
+
+		tools.addColor("Background color", function(v) {
+			scene.engine.backgroundColor = v;
+		}, scene.engine.backgroundColor);
+
+		tools.addRange("Speed", function(v) {
+			scene.speed = v;
+		}, scene.speed);
+
+		// BUILD scene tree
+
+		function makeItem(o:PrefabElement) : hide.comp.IconTree.IconTreeItem<PrefabElement> {
+			var p = o.getHideProps();
+			return {
+				data : o,
+				text : o.name,
+				icon : "fa fa-"+p.icon,
+				children : o.iterator().hasNext(),
+				state : { opened : true },
+			};
+		}
+		tree.get = function(o:PrefabElement) {
+			var objs = o == null ? data.children : Lambda.array(o);
+			return [for( o in objs ) makeItem(o)];
+		};
+		tree.root.parent().contextmenu(function(e) {
+			e.preventDefault();
+			var current = tree.getCurrentOver();
+			tree.setSelection(current == null ? [] : [current]);
+
+			var registered = new Array<hide.comp.ContextMenu.ContextMenuItem>();
+			var allRegs = @:privateAccess hide.prefab.Library.registeredElements;
+			for( ptype in allRegs.keys() ) {
+				if( ptype == "prefab" ) continue;
+				var pcl = allRegs.get(ptype);
+				var props = Type.createEmptyInstance(pcl).getHideProps();
+				registered.push({
+					label : props.name,
+					click : function() {
+
+						function make() {
+							var p = Type.createInstance(pcl, [current == null ? data : current]);
+							@:privateAccess p.type = ptype;
+							p.name = allocName(ptype);
+							return p;
+						}
+
+						if( props.fileSource != null )
+							ide.chooseFile(props.fileSource, function(path) {
+								if( path == null ) return;
+								var p = make();
+								p.source = path;
+								addObject(p);
+							});
+						else
+							addObject(make());
+					}
+				});
+			}
+
+
+			new hide.comp.ContextMenu([
+				{ label : "New...", menu : registered },
+				{ label : "Delete", enabled : current != null, click : function() {
+					function deleteRec(roots:Array<PrefabElement>) {
+						for( o in roots ) {
+							if( o == current ) {
+								properties.clear();
+								var index = roots.indexOf(o);
+								roots.remove(o);
+								undo.change(Custom(function(undo) {
+									if( undo ) roots.insert(index, o) else roots.remove(o);
+									refresh();
+								}));
+								refresh();
+								return;
+							}
+							@:privateAccess deleteRec(o.children);
+						}
+					}
+					deleteRec(data.children);
+				} },
+			]);
+		});
+		tree.init();
+		tree.onClick = selectObject;
+		scene.onResize = function() {
+			scene.s2d.x = scene.s2d.width >> 1;
+			scene.s2d.y = scene.s2d.height >> 1;
+		};
+		scene.onResize();
+	}
+
+	function update(dt:Float) {
+		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.direction.set(
+				Math.cos(angle) * lightDirection.x - Math.sin(angle) * lightDirection.y,
+				Math.sin(angle) * lightDirection.x + Math.cos(angle) * lightDirection.y,
+				lightDirection.z
+			);
+		}
+	}
+
+	static var _ = FileTree.registerExtension(Prefab,["prf"],{ icon : "sitemap", createNew : "Prefab" });
+
+}

+ 0 - 419
hide/view/SceneEditor.hx

@@ -1,419 +0,0 @@
-package hide.view;
-import hxd.fmt.s3d.Data;
-
-class SceneEditor extends FileView {
-
-	var content : hxd.fmt.s3d.Library;
-	var objRoot : h3d.scene.Object;
-	var tabs : hide.comp.Tabs;
-
-	var tools : hide.comp.Toolbar;
-	var scene : hide.comp.Scene;
-	var control : h3d.scene.CameraController;
-	var properties : hide.comp.PropsEditor;
-	var light : h3d.scene.DirLight;
-	var lightDirection = new h3d.Vector( 1, 2, -4 );
-	var tree : hide.comp.IconTree<BaseObject>;
-
-	override function getDefaultContent() {
-		return haxe.io.Bytes.ofString(ide.toJSON(new hxd.fmt.s3d.Library().save()));
-	}
-
-	override function save() {
-		sys.io.File.saveContent(getPath(), ide.toJSON(content.save()));
-	}
-
-	override function onDisplay() {
-
-		root.html('
-			<div class="flex vertical">
-				<div class="toolbar"></div>
-				<div class="flex">
-					<div class="scene">
-					</div>
-					<div class="tabs">
-						<div class="tab" name="Scene" icon="sitemap">
-							<div class="hide-block">
-								<div class="hide-list">
-									<div class="tree"></div>
-								</div>
-							</div>
-							<div class="props"></div>
-						</div>
-						<div class="tab" name="Properties" icon="gears">
-						</div>
-					</div>
-				</div>
-			</div>
-		');
-		tools = new hide.comp.Toolbar(root.find(".toolbar"));
-		tabs = new hide.comp.Tabs(root.find(".tabs"));
-		properties = new hide.comp.PropsEditor(root.find(".props"), undo);
-		scene = new hide.comp.Scene(root.find(".scene"));
-		scene.onReady = init;
-		tree = new hide.comp.IconTree(root.find(".tree"));
-	}
-
-	function refresh( ?callb ) {
-		objRoot.remove();
-		objRoot = content.makeInstance();
-		scene.s3d.addChild(objRoot);
-		scene.init(props);
-		tree.refresh(callb);
-	}
-
-	function allocName( prefix : String ) {
-		var id = 0;
-		while( objRoot.getObjectByName(prefix + id) != null )
-			id++;
-		return prefix + id;
-	}
-
-	function selectObject( elt : BaseObject ) {
-		var obj = objRoot.getObjectByName(elt.name);
-
-		properties.clear();
-
-		if( obj != null )
-			properties.add(new Element('
-			<div class="group" name="Position">
-				<dl>
-					<dt>Name</dt><dd><input field="name"></dd>
-					<dt>X</dt><dd><input type="range" min="-10" max="10" field="x"/></dd>
-					<dt>Y</dt><dd><input type="range" min="-10" max="10" field="y"/></dd>
-					<dt>Z</dt><dd><input type="range" min="-10" max="10" field="z"/></dd>
-					<dt>ScaleX</dt><dd><input type="range" min="0" max="5" field="scaleX"/></dd>
-					<dt>ScaleY</dt><dd><input type="range" min="0" max="5" field="scaleY"/></dd>
-					<dt>ScaleZ</dt><dd><input type="range" min="0" max="5" field="scaleZ"/></dd>
-					<dt>Visible</dt><dd><input type="checkbox" field="visible"/></dd>
-				</dl>
-			</div>
-			'),obj, function(name) {
-				elt.x = obj.x;
-				elt.y = obj.y;
-				elt.z = obj.z;
-				elt.name = obj.name;
-				elt.scaleX = obj.scaleX;
-				elt.scaleY = obj.scaleY;
-				elt.scaleZ = obj.scaleZ;
-				if( elt.x == 0 ) Reflect.deleteField(elt,"x");
-				if( elt.y == 0 ) Reflect.deleteField(elt,"y");
-				if( elt.z == 0 ) Reflect.deleteField(elt,"z");
-				if( elt.scaleX == 1 ) Reflect.deleteField(elt, "scaleX");
-				if( elt.scaleY == 1 ) Reflect.deleteField(elt, "scaleY");
-				if( elt.scaleZ == 1 ) Reflect.deleteField(elt, "scaleZ");
-				if( name == "name" )
-					tree.refresh();
-			});
-
-		switch( elt.type ) {
-		case Object:
-			var elt : ObjectProperties = cast elt;
-			var props = properties.add(new Element('
-				<dl>
-					<dt>Animation</dt><dd><select><option value="">-- Choose --</option></select>
-					<dt title="Don\'t save animation changes">Lock</dt><dd><input type="checkbox" field="lock"></select>
-				</dl>
-			'),elt);
-
-			var select = props.find("select");
-			var anims = scene.listAnims(elt.modelPath);
-			for( a in anims )
-				new Element('<option>').attr("value", a).text(scene.animationName(a)).appendTo(select);
-			if( elt.animationPath != null ) select.val(ide.getPath(elt.animationPath));
-			select.change(function(_) {
-				var v = select.val();
-				var prev = elt.animationPath;
-				if( v == "" ) {
-					elt.animationPath = null;
-					obj.stopAnimation();
-				} else {
-					obj.playAnimation(scene.loadAnimation(v)).loop = true;
-					if( elt.lock ) return;
-					elt.animationPath = ide.makeRelative(v);
-				}
-				var newValue = elt.animationPath;
-				undo.change(Custom(function(undo) {
-					elt.animationPath = undo ? prev : newValue;
-					if( elt.animationPath == null ) {
-						obj.stopAnimation();
-						select.val("");
-					} else {
-						obj.playAnimation(scene.loadAnimation(v)).loop = true;
-						select.val(v);
-					}
-				}));
-			});
-
-		case Constraint:
-			var elt : ConstraintProperties = cast elt;
-			var props = properties.add(new Element('
-				<dl>
-					<dt>Name</dt><dd><input field="name"/></dd>
-					<dt>Source</dt><dd><select field="source"><option value="">-- Choose --</option></select>
-					<dt>Target</dt><dd><select field="attach"><option value="">-- Choose --</option></select>
-				</dl>
-			'),elt, function(field) if( field == "name" ) tree.refresh() else refresh());
-
-			for( select in [props.find("[field=source]"), props.find("[field=attach]")] ) {
-				for( path in getNamedObjects() ) {
-					var parts = path.split(".");
-					var opt = new Element("<option>").attr("value", path).html([for( p in 1...parts.length ) "&nbsp; "].join("") + parts.pop());
-					select.append(opt);
-				}
-				select.val(Reflect.field(elt, select.attr("field")));
-			}
-
-		case Trail:
-
-			var elt : ExtraProperties = cast elt;
-			var obj : h3d.scene.Trail = cast obj;
-
-
-			var props = properties.add(new Element('
-			<div class="group" name="Material">
-			</div>
-			<div class="group" name="Trail Properties">
-				<dl>
-					<dt>Angle</dt><dd><input type="range" field="angle" scale="${180/Math.PI}" min="0" max="${Math.PI*2}"/></dd>
-					<dt>Duration</dt><dd><input type="range" field="duration" min="0" max="1"/></dd>
-					<dt>Size Start</dt><dd><input type="range" field="sizeStart" min="0" max="10"/></dd>
-					<dt>Size End</dt><dd><input type="range" field="sizeEnd" min="0" max="10"/></dd>
-					<dt>Movement Min.</dt><dd><input type="range" field="movementMin" min="0" max="1"/></dd>
-					<dt>Movement Max.</dt><dd><input type="range" field="movementMax" min="0" max="1"/></dd>
-					<dt>Smoothness</dt><dd><input type="range" field="smoothness" min="0" max="1"/></dd>
-					<dt>Texture</dt><dd><input type="texture" field="texture"/></dd>
-				</dl>
-			</div>
-			'),obj, function(_) {
-				elt.data = obj.save();
-			});
-
-			properties.addMaterial( obj.material, props.find("[name=Material] > .content"), function(_) elt.data = obj.save());
-
-
-		default:
-		}
-	}
-
-	function getNamedObjects( ?exclude : h3d.scene.Object ) {
-		var out = [];
-
-		function getJoint(path:Array<String>,j:h3d.anim.Skin.Joint) {
-			path.push(j.name);
-			out.push(path.join("."));
-			for( j in j.subs )
-				getJoint(path, j);
-			path.pop();
-		}
-
-		function getRec(path:Array<String>, o:h3d.scene.Object) {
-			if( o == exclude || o.name == null ) return;
-			path.push(o.name);
-			out.push(path.join("."));
-			for( c in o )
-				getRec(path, c);
-			var sk = Std.instance(o, h3d.scene.Skin);
-			if( sk != null ) {
-				var j = sk.getSkinData();
-				for( j in j.rootJoints )
-					getJoint(path, j);
-			}
-			path.pop();
-		}
-
-		for( o in objRoot )
-			getRec([], o);
-
-		return out;
-	}
-
-	function init() {
-		content = new hxd.fmt.s3d.Library();
-		content.load(haxe.Json.parse(sys.io.File.getContent(getPath())));
-		objRoot = content.makeInstance();
-		scene.s3d.addChild(objRoot);
-
-		light = scene.s3d.find(function(o) return Std.instance(o, h3d.scene.DirLight));
-		if( light == null ) {
-			light = new h3d.scene.DirLight(new h3d.Vector(), scene.s3d);
-			light.enableSpecular = true;
-		} else
-			light = null;
-
-		control = new h3d.scene.CameraController(scene.s3d);
-
-		this.saveDisplayKey = "Scene:" + state.path;
-
-		var cam = getDisplayState("Camera");
-		if( cam == null )
-			scene.resetCamera(scene.s3d, 1.5);
-		else {
-			scene.s3d.camera.pos.set(cam.x, cam.y, cam.z);
-			scene.s3d.camera.target.set(cam.tx, cam.ty, cam.tz);
-		}
-		control.loadFromCamera();
-
-		scene.onUpdate = update;
-		scene.init(props);
-		tools.saveDisplayKey = "SceneTools";
-
-		tools.addButton("video-camera", "Reset Camera", function() {
-			scene.resetCamera(objRoot,1.5);
-			control.loadFromCamera();
-		});
-
-		tools.addToggle("sun-o", "Enable Lights/Shadows", function(v) {
-			if( !v ) {
-				for( m in objRoot.getMaterials() ) {
-					m.mainPass.enableLights = false;
-					m.shadows = false;
-				}
-			} else {
-				for( m in objRoot.getMaterials() )
-					h3d.mat.MaterialSetup.current.initModelMaterial(m);
-			}
-		},true);
-
-		tools.addColor("Background color", function(v) {
-			scene.engine.backgroundColor = v;
-		}, scene.engine.backgroundColor);
-
-		tools.addRange("Speed", function(v) {
-			scene.speed = v;
-		}, scene.speed);
-
-		// BUILD scene tree
-
-		function makeItem(o:BaseObject) : hide.comp.IconTree.IconTreeItem<BaseObject> {
-			return {
-				data : o,
-				text : o.name,
-				icon : "fa fa-"+switch( o.type ) {
-				case Object: "cube";
-				case Constraint: "lock";
-				case Particles: "snowflake-o";
-				case Trail: "toggle-on";
-				},
-				children : o.children != null && o.children.length > 0,
-				state : { opened : true },
-			};
-		}
-		tree.get = function(o:BaseObject) {
-			var objs = o == null ? content.data.content : o.children;
-			return [for( o in objs ) makeItem(o)];
-		};
-		tree.root.parent().contextmenu(function(e) {
-			e.preventDefault();
-			var current = tree.getCurrentOver();
-			tree.setSelection(current == null ? [] : [current]);
-
-			new hide.comp.ContextMenu([
-				{ label : "New...", menu : [
-					{ label : "Model", click : function() {
-						ide.chooseFile(["fbx", "hmd"], function(path) {
-							if( path == null ) return;
-							var props : ObjectProperties = {
-								type : Object,
-								name : allocName("Object"),
-								modelPath : path,
-							};
-							addObject(props, current);
-						});
-					} },
-					{ label : "Particles", click : function() {
-						var parts = new h3d.parts.GpuParticles();
-						parts.addGroup();
-						var props : ExtraProperties = {
-							type : Particles,
-							name : allocName("Particles"),
-							data : parts.save(),
-						};
-						addObject(props, current);
-					} },
-					{ label : "Trail", click : function() {
-						var props : ExtraProperties = {
-							type : Trail,
-							name : allocName("Trail"),
-							data : new h3d.scene.Trail().save(),
-						};
-						addObject(props, current);
-					} },
-					{ label : "Constraint", click : function() {
-						var props : ConstraintProperties = {
-							type : Constraint,
-							name : allocName("Constraint"),
-							source : "",
-							attach : "",
-						};
-						addObject(props, current);
-					} },
-				] },
-				{ label : "Delete", enabled : current != null, click : function() {
-					function deleteRec(roots:Array<BaseObject>) {
-						for( o in roots ) {
-							if( o == current ) {
-								properties.clear();
-								var index = roots.indexOf(o);
-								roots.remove(o);
-								undo.change(Custom(function(undo) {
-									if( undo ) roots.insert(index, o) else roots.remove(o);
-									refresh();
-								}));
-								refresh();
-								return;
-							}
-							if( o.children != null ) deleteRec(o.children);
-						}
-					}
-					deleteRec(content.data.content);
-				} },
-			]);
-		});
-		tree.init();
-		tree.onClick = selectObject;
-	}
-
-	function addObject( props : BaseObject, parent : BaseObject ) {
-		var roots = content.data.content;
-		if( parent != null ) {
-			roots = parent.children;
-			if( roots == null ) parent.children = roots = [];
-		}
-		roots.push(props);
-		undo.change(Custom(function(undo) {
-			if( undo ) {
-				roots.remove(props);
-				if( roots.length == 0 && parent != null ) Reflect.deleteField(parent, "children");
-			} else {
-				roots.push(props);
-				if( parent != null ) parent.children = roots;
-			}
-			refresh();
-		}));
-		refresh(function() {
-			tree.setSelection([props]);
-			selectObject(props);
-		});
-		if( parent == null && roots.length == 1 ) {
-			scene.resetCamera(objRoot, 1.5);
-			control.loadFromCamera();
-		}
-	}
-
-	function update(dt:Float) {
-		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.direction.set(
-				Math.cos(angle) * lightDirection.x - Math.sin(angle) * lightDirection.y,
-				Math.sin(angle) * lightDirection.x + Math.cos(angle) * lightDirection.y,
-				lightDirection.z
-			);
-		}
-	}
-
-	static var _ = FileTree.registerExtension(SceneEditor,["s3d"],{ icon : "sitemap", createNew : "Scene 3D" });
-
-}