Browse Source

Add FX 2D

Tom Spira 6 years ago
parent
commit
73d52982fd

+ 145 - 41
hide/view/FXEditor.hx

@@ -73,42 +73,57 @@ private class FXSceneEditor extends hide.comp.SceneEditor {
 		var allTypes = super.getNewContextMenu(current, onMake);
 
 		var menu = [];
-		for(name in ["Group", "Polygon", "Model", "Shaders", "Emitter"]) {
-			var item = allTypes.find(i -> i.label == name);
-			if(item == null) continue;
-			allTypes.remove(item);
-			menu.push(item);
-		}
-		if(current != null) {
+		if (@:privateAccess parent.is2D) {
+			for(name in ["Group 2D", "Bitmap", "Shaders", "Shader Graph"]) {
+				var item = allTypes.find(i -> i.label == name);
+				if(item == null) continue;
+				allTypes.remove(item);
+				menu.push(item);
+			}
+			if(current != null) {
+				menu.push({
+					label: "Animation",
+					menu: parent.getNewTrackMenu(current)
+				});
+			}
+		} else {
+			for(name in ["Group", "Polygon", "Model", "Shaders", "Emitter"]) {
+				var item = allTypes.find(i -> i.label == name);
+				if(item == null) continue;
+				allTypes.remove(item);
+				menu.push(item);
+			}
+			if(current != null) {
+				menu.push({
+					label: "Animation",
+					menu: parent.getNewTrackMenu(current)
+				});
+			}
+
 			menu.push({
-				label: "Animation",
-				menu: parent.getNewTrackMenu(current)
+				label: "Material",
+				menu: [
+					getNewTypeMenuItem("material", current, onMake, "Default"),
+					getNewTypeMenuItem("material", current, function (p) {
+						// TODO: Move material presets to props.json
+						p.props = {
+							PBR: {
+								mode: "Overlay",
+								blend: "None",
+								shadows: false
+							}
+						}
+						if(onMake != null) onMake(p);
+					}, "Unlit")
+				]
+			});
+			menu.sort(function(l1,l2) return Reflect.compare(l1.label,l2.label));
+			menu.push({label: null, isSeparator: true});
+			menu.push({
+				label: "Other",
+				menu: allTypes
 			});
 		}
-
-		menu.push({
-			label: "Material",
-			menu: [
-				getNewTypeMenuItem("material", current, onMake, "Default"),
-				getNewTypeMenuItem("material", current, function (p) {
-					// TODO: Move material presets to props.json
-					p.props = {
-						PBR: {
-							mode: "Overlay",
-							blend: "None",
-							shadows: false
-						}
-					}
-					if(onMake != null) onMake(p);
-				}, "Unlit")
-			]
-		});
-		menu.sort(function(l1,l2) return Reflect.compare(l1.label,l2.label));
-		menu.push({label: null, isSeparator: true});
-		menu.push({
-			label: "Other",
-			menu: allTypes
-		});
 		return menu;
 	}
 }
@@ -116,7 +131,8 @@ private class FXSceneEditor extends hide.comp.SceneEditor {
 class FXEditor extends FileView {
 
 	var sceneEditor : FXSceneEditor;
-	var data : hrt.prefab.fx.FX;
+	var data : hrt.prefab.fx.BaseFX;
+	var is2D : Bool = false;
 	var tabs : hide.comp.Tabs;
 	var fxprops : hide.comp.PropsEditor;
 
@@ -173,9 +189,15 @@ class FXEditor extends FileView {
 		saveDisplayKey = "FXScene/" + getPath().split("\\").join("/").substr(0,-1);
 		currentTime = 0.;
 		xOffset = -timelineLeftMargin / xScale;
-		data = new hrt.prefab.fx.FX();
 		var content = sys.io.File.getContent(getPath());
-		data.loadData(haxe.Json.parse(content));
+		var json = haxe.Json.parse(content);
+		if (json.type == "fx")
+			data = new hrt.prefab.fx.FX();
+		else {
+			is2D = true;
+			data = new hrt.prefab.fx.FX2D();
+		}
+		data.loadData(json);
 		currentSign = haxe.crypto.Md5.encode(content);
 
 		element.html('
@@ -1099,7 +1121,8 @@ class FXEditor extends FileView {
 	}
 
 	public function getNewTrackMenu(elt: PrefabElement) : Array<hide.comp.ContextMenu.ContextMenuItem> {
-		var objElt = Std.downcast(elt, hrt.prefab.Object3D);
+		var obj3dElt = Std.downcast(elt, hrt.prefab.Object3D);
+		var obj2dElt = Std.downcast(elt, hrt.prefab.Object2D);
 		var shaderElt = Std.downcast(elt, hrt.prefab.Shader);
 		var emitterElt = Std.downcast(elt, hrt.prefab.fx.Emitter);
 		var menuItems : Array<hide.comp.ContextMenu.ContextMenuItem> = [];
@@ -1136,10 +1159,34 @@ class FXEditor extends FileView {
 		var hslTracks : Void -> Array<PropTrackDef> = () -> [{name: "h", def: 0.0}, {name: "s", clamp: [0., 1.], def: 0.0}, {name: "l", clamp: [0., 1.], def: 1.0}];
 		var alphaTrack : Void -> Array<PropTrackDef> = () -> [{name: "a", clamp: [0., 1.], def: 1.0}];
 		var xyzwTracks : Int -> Array<PropTrackDef> = (n) -> [{name: "x"}, {name: "y"}, {name: "z"}, {name: "w"}].slice(0, n);
-		var scaleTracks = groupedTracks("scale", xyzwTracks(3));
-		scaleTracks.unshift(trackItem("Uniform", [{name: "scale"}]));
 
-		if(objElt != null) {
+		if (obj2dElt != null) {
+			var scaleTracks = groupedTracks("scale", xyzwTracks(2));
+			scaleTracks.unshift(trackItem("Uniform", [{name: "scale"}]));
+			menuItems.push({
+				label: "Position",
+				menu: groupedTracks("position", xyzwTracks(2)),
+			});
+			menuItems.push({
+				label: "Rotation",
+				menu: [trackItem("X", [{name: "x"}], "rotation")],
+			});
+			menuItems.push({
+				label: "Scale",
+				menu: scaleTracks,
+			});
+			menuItems.push({
+				label: "Color",
+				menu: [
+					trackItem("HSL", hslTracks(), "color"),
+					trackItem("Alpha", alphaTrack(), "color")
+				]
+			});
+			menuItems.push(trackItem("Visibility", [{name: "visibility", clamp: [0., 1.]}]));
+		}
+		if(obj3dElt != null) {
+			var scaleTracks = groupedTracks("scale", xyzwTracks(3));
+			scaleTracks.unshift(trackItem("Uniform", [{name: "scale"}]));
 			menuItems.push({
 				label: "Position",
 				menu: groupedTracks("position", xyzwTracks(3)),
@@ -1247,7 +1294,63 @@ class FXEditor extends FileView {
 		grid.lineStyle(0);
 	}
 
-	function onUpdate(dt:Float) {
+	function onUpdate(dt : Float) {
+		if (is2D)
+			onUpdate2D(dt);
+		else
+			onUpdate3D(dt);
+	}
+
+	function onUpdate2D(dt:Float) {
+		var anim : hrt.prefab.fx.FX2D.FX2DAnimation = null;
+		var ctx = sceneEditor.getContext(data);
+		if(ctx != null && ctx.local2d != null) {
+			anim = Std.downcast(ctx.local2d, hrt.prefab.fx.FX2D.FX2DAnimation);
+		}
+		if(!pauseButton.isDown()) {
+			currentTime += scene.speed * dt;
+			if(timeLineEl != null)
+				timeLineEl.css({left: xt(currentTime)});
+			if(currentTime >= previewMax) {
+				currentTime = previewMin;
+
+				//if(data.scriptCode != null && data.scriptCode.length > 0)
+					//sceneEditor.refreshScene(); // This allow to reset the scene when values are modified causes edition issues, solves
+
+				anim.setRandSeed(Std.random(0xFFFFFF));
+			}
+		}
+
+		if(anim != null) {
+			anim.setTime(currentTime);
+		}
+
+		if(statusText != null) {
+			var lines : Array<String> = [
+				'Time: ${Math.round(currentTime*1000)} ms',
+				'Scene objects: ${scene.s2d.getObjectsCount()}',
+				'Drawcalls: ${h3d.Engine.getCurrent().drawCalls}',
+			];
+			statusText.text = lines.join("\n");
+		}
+
+		var cam = scene.s3d.camera;
+		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( autoSync && (currentVersion != undo.currentID || lastSyncChange != properties.lastChange) ) {
+			save();
+			lastSyncChange = properties.lastChange;
+			currentVersion = undo.currentID;
+		}
+	}
+
+	function onUpdate3D(dt:Float) {
 		var anim : hrt.prefab.fx.FX.FXAnimation = null;
 		var ctx = sceneEditor.getContext(data);
 		if(ctx != null && ctx.local3d != null) {
@@ -1322,4 +1425,5 @@ class FXEditor extends FileView {
 	}
 
 	static var _ = FileTree.registerExtension(FXEditor, ["fx"], { icon : "sitemap", createNew : "FX" });
+	static var _2d = FileTree.registerExtension(FXEditor, ["fx2d"], { icon : "sitemap", createNew : "FX 2D" });
 }

+ 94 - 0
hrt/prefab/Bitmap.hx

@@ -0,0 +1,94 @@
+package hrt.prefab;
+
+class Bitmap extends Object2D {
+
+	// parameters
+	var color : Int = 0;
+
+	var src : String;
+
+	var dx : Float = 0;
+	var dy : Float = 0;
+
+	var bmp : h2d.Bitmap;
+	var tex : h3d.mat.Texture;
+
+	override public function load(v:Dynamic) {
+		super.load(v);
+		this.color = v.color;
+		this.src = v.src;
+		this.dx = v.dx;
+		this.dy = v.dy;
+	}
+
+	override function save() {
+		var o : Dynamic = super.save();
+		o.color = color;
+		o.src = src;
+		o.dx = dx;
+		o.dy = dy;
+		return o;
+	}
+
+	override function updateInstance( ctx: Context, ?propName : String ) {
+		super.updateInstance(ctx, propName);
+		bmp.visible = visible;
+		if (propName == null || propName == "src") {
+			if (tex != null) {
+				tex = null;
+			}
+			if (src != null) {
+				tex = ctx.loadTexture(src);
+				bmp.tile = h2d.Tile.fromTexture(this.tex);
+			} else {
+				bmp.tile = null;
+			}
+		}
+		bmp.color = h3d.Vector.fromColor(color);
+		bmp.color.w = 1;
+		if (bmp.tile != null) {
+			var cRatio = getCenterRatio(dx, dy);
+			bmp.tile.setCenterRatio(cRatio[0], cRatio[1]);
+		}
+		bmp.blendMode = blendMode;
+	}
+
+	override function makeInstance(ctx:Context):Context {
+		ctx = ctx.clone(this);
+		bmp = new h2d.Bitmap(null, ctx.local2d);
+		bmp.smooth = true;
+		ctx.local2d = bmp;
+		ctx.local2d.name = name;
+		ctx.cleanup = function() { bmp = null; }
+		updateInstance(ctx);
+		return ctx;
+	}
+
+	static public function getCenterRatio(dx : Float, dy : Float) {
+		return [0.5 + hxd.Math.clamp(dx, -0.5, 0.5), 0.5 + hxd.Math.clamp(dy, -0.5, 0.5)];
+	}
+
+	#if editor
+	override function edit( ctx : EditContext ) {
+		super.edit(ctx);
+
+		ctx.properties.add(new hide.Element('<div class="group" name="Parameters">
+			<dl>
+				<dt>Color</dt><dd><input type="color" field="color" /></dd>
+				<dt>Background</dt><dd><input type="texturepath" field="src" style="width:165px"/></dd>
+				<dt>Bg Pivot DX</dt><dd><input type="range" min="-0.5" max="0.5" field="dx"/></dd>
+				<dt>Bg Pivot DY</dt><dd><input type="range" min="-0.5" max="0.5" field="dy"/></dd>
+			</dl></div>'), this, function(pname) {
+			ctx.onChange(this, pname);
+		});
+	}
+
+	override function getHideProps() : HideProps {
+		return { icon : "square", name : "Bitmap" };
+	}
+
+	#end
+
+	static var _ = Library.register("bitmap", Bitmap);
+
+}

+ 140 - 0
hrt/prefab/Object2D.hx

@@ -0,0 +1,140 @@
+package hrt.prefab;
+import hxd.Math;
+using Lambda;
+
+class Object2D extends Prefab {
+
+	public var x : Float = 0.;
+	public var y : Float = 0.;
+	public var scaleX : Float = 1.;
+	public var scaleY : Float = 1.;
+	public var rotation : Float = 0.;
+
+	public var visible : Bool = true;
+	public var blendMode : h2d.BlendMode = None;
+
+	public function loadTransform(t) {
+		x = t.x;
+		y = t.y;
+		scaleX = t.scaleX;
+		scaleY = t.scaleY;
+		rotation = t.rotation;
+	}
+
+	public function saveTransform() {
+		return { x : x, y : y, scaleX : scaleX, scaleY : scaleY, rotation : rotation };
+	}
+
+	override function load( obj : Dynamic ) {
+		x = obj.x == null ? 0. : obj.x;
+		y = obj.y == null ? 0. : obj.y;
+
+		scaleX = obj.scaleX == null ? 1. : obj.scaleX;
+		scaleY = obj.scaleY == null ? 1. : obj.scaleY;
+
+		rotation = obj.rotation == null ? 0. : obj.rotation;
+
+		visible = obj.visible == null ? true : obj.visible;
+		
+		blendMode = obj.blendMode == null ? None : std.Type.createEnum(h2d.BlendMode, obj.blendMode);
+	}
+
+	override function makeInstance(ctx:Context):Context {
+		ctx = ctx.clone(this);
+		ctx.local2d = new h2d.Object(ctx.local2d);
+		ctx.local2d.name = name;
+		updateInstance(ctx);
+		return ctx;
+	}
+
+	override function save() {
+		var o : Dynamic = {};
+		if( x != 0 ) o.x = x;
+		if( y != 0 ) o.y = y;
+		if( scaleX != 1 ) o.scaleX = scaleX;
+		if( scaleY != 1 ) o.scaleY = scaleY;
+		if( rotation != 0 ) o.rotation = rotation;
+		if( !visible ) o.visible = visible;
+		if ( blendMode != None) o.blendMode = blendMode.getName();
+		return o;
+	}
+
+	public function getTransform() {
+		var m = new h2d.col.Matrix();
+		m.initScale(scaleX, scaleY);
+		m.rotate(Math.degToRad(rotation));
+		m.translate(x, y);
+		return m;
+	}
+
+	public function applyPos( o : h2d.Object ) {
+		o.x = x;
+		o.y = y;
+		o.scaleX = scaleX;
+		o.scaleY = scaleY;
+		o.rotation = Math.degToRad(rotation);
+	}
+
+	override function updateInstance( ctx: Context, ?propName : String ) {
+		var o = ctx.local2d;
+		o.x = x;
+		o.y = y;
+		if(propName == null || propName.indexOf("scale") == 0) {
+			o.scaleX = scaleX;
+			o.scaleY = scaleY;
+		}
+		if(propName == null || propName.indexOf("rotation") == 0)
+			o.rotation = Math.degToRad(rotation);
+		if(propName == null || propName == "visible")
+			o.visible = visible;
+
+		if(propName == null || propName == "blendMode")
+			o.blendMode = blendMode;
+	}
+
+	override function removeInstance(ctx: Context):Bool {
+		if(ctx.local2d != null)
+			ctx.local2d.remove();
+		return true;
+	}
+
+	#if editor
+	override function edit( ctx : EditContext ) {
+		var props = new hide.Element('
+			<div class="group" name="Position">
+				<dl>
+					<dt>X</dt><dd><input type="range" min="-10" max="10" value="0" field="x"/></dd>
+					<dt>Y</dt><dd><input type="range" min="-10" max="10" value="0" field="y"/></dd>
+					<dt>Scale X</dt><dd><input type="range" min="0" max="5" value="1" field="scaleX"/></dd>
+					<dt>Scale Y</dt><dd><input type="range" min="0" max="5" value="1" field="scaleY"/></dd>
+					<dt>Rotation</dt><dd><input type="range" min="-180" max="180" value="0" field="rotation" /></dd>
+				</dl>
+			</div>
+			<div class="group" name="Display">
+				<dl>
+					<dt>Visible</dt><dd><input type="checkbox" field="visible"/></dd>
+					<dt>Blend Mode</dt><dd><select field="blendMode"/></dd></dd>
+				</dl>
+			</div>
+		');
+		ctx.properties.add(props, this, function(pname) {
+			ctx.onChange(this, pname);
+		});
+	}
+
+	override function getHideProps() : HideProps {
+		// Check children
+		return {
+			icon : children == null || children.length > 0 ? "folder-open" : "genderless",
+			name : "Group 2D"
+		};
+	}
+	#end
+
+	override function getDefaultName() {
+		return type == "object2D" ? "group2D" : super.getDefaultName();
+	}
+
+	static var _ = Library.register("object2D", Object2D);
+
+}

+ 6 - 1
hrt/prefab/Shader.hx

@@ -63,6 +63,11 @@ class Shader extends Prefab {
 			shader.hscriptSet(v.variable.name, v.value);
 			#end
 		}
+		if(ctx.local2d != null) {
+			var drawable = Std.downcast(ctx.local2d, h2d.Drawable);
+			if (drawable != null)
+				drawable.addShader(shader);
+		}
 		if(ctx.local3d != null) {
 			for(m in ctx.local3d.getMaterials()) { // TODO: Only add to self materials, not all children materials
 				m.mainPass.addShader(shader);
@@ -141,7 +146,7 @@ class Shader extends Prefab {
 	}
 
 	override function getHideProps() : HideProps {
-		return { icon : "cog", name : "Shader", fileSource : ["hx"], allowParent : function(p) return p.to(Object3D) != null };
+		return { icon : "cog", name : "Shader", fileSource : ["hx"], allowParent : function(p) return p.to(Object2D) != null || p.to(Object3D) != null };
 	}
 
 	#end

+ 1 - 1
hrt/prefab/ShaderGraph.hx

@@ -26,7 +26,7 @@ class ShaderGraph extends Shader {
 
 	#if editor
 	override function getHideProps() : HideProps {
-		return { icon : "cog", name : "Shader Graph", fileSource : ["hlshader"], allowParent : function(p) return p.to(Object3D) != null };
+		return { icon : "cog", name : "Shader Graph", fileSource : ["hlshader"], allowParent : function(p) return p.to(Object2D) != null || p.to(Object3D) != null };
 	}
 
 	override function edit( ctx : EditContext ) {

+ 164 - 0
hrt/prefab/fx/BaseFX.hx

@@ -0,0 +1,164 @@
+package hrt.prefab.fx;
+import hrt.prefab.Curve;
+import hrt.prefab.Prefab as PrefabElement;
+
+typedef ShaderParam = {
+	def: hxsl.Ast.TVar,
+	value: Value
+};
+
+typedef ShaderParams = Array<ShaderParam>;
+
+class ShaderAnimation extends Evaluator {
+	public var params : ShaderParams;
+	public var shader : hxsl.DynamicShader;
+
+	public function setTime(time: Float) {
+		for(param in params) {
+			var v = param.def;
+			switch(v.type) {
+				case TFloat:
+					var val = getFloat(param.value, time);
+					shader.setParamValue(v, val);
+				case TInt:
+					var val = hxd.Math.round(getFloat(param.value, time));
+					shader.setParamValue(v, val);
+				case TBool:
+					var val = getFloat(param.value, time) >= 0.5;
+					shader.setParamValue(v, val);
+				case TVec(_, VFloat):
+					var val = getVector(param.value, time);
+					shader.setParamValue(v, val);
+				default:
+			}
+		}
+	}
+}
+
+typedef ObjectAnimation = {
+	?elt: hrt.prefab.Object3D,
+	?obj: h3d.scene.Object,
+	?elt2d: hrt.prefab.Object2D,
+	?obj2d: h2d.Object,
+	events: Array<hrt.prefab.fx.Event.EventInstance>,
+	?position: Value,
+	?scale: Value,
+	?rotation: Value,
+	?color: Value,
+	?visibility: Value
+};
+
+class BaseFX extends hrt.prefab.Library {
+
+	public var duration : Float;
+	public var scriptCode : String;
+	public var cullingRadius : Float;
+
+	public function new() {
+		super();
+		duration = 5.0;
+		scriptCode = null;
+		cullingRadius = 1000;
+	}
+
+	override function save() {
+		var obj : Dynamic = super.save();
+		obj.duration = duration;
+		return obj;
+	}
+
+	override function load( obj : Dynamic ) {
+		super.load(obj);
+		duration = obj.duration == null ? 5.0 : obj.duration;
+	}
+
+	public function refreshObjectAnims(ctx: Context) { }
+
+	public static function makeShaderParams(ctx: Context, shaderElt: hrt.prefab.Shader) {
+		shaderElt.loadShaderDef(ctx);
+		var shaderDef = shaderElt.shaderDef;
+		if(shaderDef == null)
+			return null;
+
+		var ret : ShaderParams = [];
+
+		for(v in shaderDef.shader.data.vars) {
+			if(v.kind != Param)
+				continue;
+
+			var prop = Reflect.field(shaderElt.props, v.name);
+			if(prop == null)
+				prop = hrt.prefab.Shader.getDefault(v.type);
+
+			var curves = Curve.getCurves(shaderElt, v.name);
+			if(curves == null || curves.length == 0)
+				continue;
+
+			switch(v.type) {
+				case TVec(_, VFloat) :
+					var isColor = v.name.toLowerCase().indexOf("color") >= 0;
+					var val = isColor ? Curve.getColorValue(curves) : Curve.getVectorValue(curves);
+					ret.push({
+						def: v,
+						value: val
+					});
+
+				default:
+					var base = 1.0;
+					if(Std.is(prop, Float) || Std.is(prop, Int))
+						base = cast prop;
+					var curve = Curve.getCurve(shaderElt, v.name);
+					var val = Value.VConst(base);
+					if(curve != null)
+						val = Value.VCurveScale(curve, base);
+					ret.push({
+						def: v,
+						value: val
+					});
+			}
+		}
+
+		return ret;
+	}
+
+	public static function getShaderAnims(ctx: Context, elt: PrefabElement, anims: Array<ShaderAnimation>) {
+		if(Std.downcast(elt, hrt.prefab.fx.Emitter) == null) {
+			for(c in elt.children) {
+				getShaderAnims(ctx, c, anims);
+			}
+		}
+
+		var shader = elt.to(hrt.prefab.Shader);
+		if(shader == null)
+			return;
+
+		for(shCtx in ctx.shared.getContexts(elt)) {
+			if(shCtx.custom == null) continue;
+			var anim: ShaderAnimation = new ShaderAnimation(new hxd.Rand(0));
+			anim.shader = shCtx.custom;
+			anim.params = makeShaderParams(ctx, shader);
+			anims.push(anim);
+		}
+	}
+
+	function getFXRoot( ctx : Context, elt : PrefabElement ) : PrefabElement {
+		if( elt.name == "FXRoot" )
+			return elt;
+		else {
+			for(c in elt.children) {
+				var elt = getFXRoot(ctx, c);
+				if(elt != null) return elt;
+			}
+		}
+		return null;
+	}
+
+	function getConstraints( ctx : Context, elt : PrefabElement, constraints : Array<hrt.prefab.Constraint>){
+		var co = Std.downcast(elt, hrt.prefab.Constraint);
+		if(co != null)
+			constraints.push(co);
+		else
+			for(c in elt.children)
+				getConstraints(ctx, c, constraints);
+	}
+} 

+ 2 - 2
hrt/prefab/fx/Emitter.hx

@@ -1,6 +1,6 @@
 package hrt.prefab.fx;
 import hrt.prefab.Curve;
-import hrt.prefab.fx.FX.ShaderAnimation;
+import hrt.prefab.fx.BaseFX.ShaderAnimation;
 using Lambda;
 
 enum SimulationSpace {
@@ -471,7 +471,7 @@ class EmitterObject extends h3d.scene.Object {
 				if( !shader.enabled ) continue;
 				var shCtx = shader.makeInstance(template);
 				if( shCtx == null ) continue;
-				hrt.prefab.fx.FX.getShaderAnims(template, shader, shaderAnims);
+				hrt.prefab.fx.BaseFX.getShaderAnims(template, shader, shaderAnims);
 			}
 
 			// Animated textures animations

+ 14 - 159
hrt/prefab/fx/FX.hx

@@ -1,50 +1,8 @@
 package hrt.prefab.fx;
 import hrt.prefab.Curve;
 import hrt.prefab.Prefab as PrefabElement;
-
-typedef ShaderParam = {
-	def: hxsl.Ast.TVar,
-	value: Value
-};
-
-typedef ShaderParams = Array<ShaderParam>;
-
-class ShaderAnimation extends Evaluator {
-	public var params : ShaderParams;
-	public var shader : hxsl.DynamicShader;
-
-	public function setTime(time: Float) {
-		for(param in params) {
-			var v = param.def;
-			switch(v.type) {
-				case TFloat:
-					var val = getFloat(param.value, time);
-					shader.setParamValue(v, val);
-				case TInt:
-					var val = hxd.Math.round(getFloat(param.value, time));
-					shader.setParamValue(v, val);
-				case TBool:
-					var val = getFloat(param.value, time) >= 0.5;
-					shader.setParamValue(v, val);
-				case TVec(_, VFloat):
-					var val = getVector(param.value, time);
-					shader.setParamValue(v, val);
-				default:
-			}
-		}
-	}
-}
-
-typedef ObjectAnimation = {
-	elt: hrt.prefab.Object3D,
-	obj: h3d.scene.Object,
-	events: Array<hrt.prefab.fx.Event.EventInstance>,
-	?position: Value,
-	?scale: Value,
-	?rotation: Value,
-	?color: Value,
-	?visibility: Value
-};
+import hrt.prefab.fx.BaseFX.ObjectAnimation;
+import hrt.prefab.fx.BaseFX.ShaderAnimation;
 
 class FXAnimation extends h3d.scene.Object {
 
@@ -56,7 +14,6 @@ class FXAnimation extends h3d.scene.Object {
 	public var duration : Float;
 	public var cullingRadius : Float;
 
-	public var loopAnims : Bool;
 	public var objects: Array<ObjectAnimation> = [];
 	public var shaderAnims : Array<ShaderAnimation> = [];
 	public var emitters : Array<hrt.prefab.fx.Emitter.EmitterObject> = [];
@@ -204,25 +161,16 @@ class FXAnimation extends h3d.scene.Object {
 	}
 }
 
-class FX extends hrt.prefab.Library {
-
-	public var duration : Float;
-	public var loopAnims : Bool;
-	public var scriptCode : String;
-	public var cullingRadius : Float;
+class FX extends BaseFX {
 
 	public function new() {
 		super();
 		type = "fx";
-		duration = 5.0;
 		cullingRadius = 3.0;
-		loopAnims = true;
 	}
 
 	override function save() {
 		var obj : Dynamic = super.save();
-		obj.duration = duration;
-		obj.loopAnims = loopAnims;
 		obj.cullingRadius = cullingRadius;
 		if( scriptCode != "" ) obj.scriptCode = scriptCode;
 		return obj;
@@ -230,13 +178,17 @@ class FX extends hrt.prefab.Library {
 
 	override function load( obj : Dynamic ) {
 		super.load(obj);
-		duration = obj.duration == null ? 5.0 : obj.duration;
-		loopAnims = obj.loopAnims == null ? true : obj.loopAnims;
 		if(obj.cullingRadius != null)
 			cullingRadius = obj.cullingRadius;
 		scriptCode = obj.scriptCode;
 	}
 
+	override public function refreshObjectAnims(ctx: Context) {
+		var fxanim = Std.downcast(ctx.local3d, FXAnimation);
+		fxanim.objects = [];
+		getObjAnimations(ctx, this, fxanim.objects);
+	}
+
 	static function getObjAnimations(ctx:Context, elt: PrefabElement, anims: Array<ObjectAnimation>) {
 		if(Std.downcast(elt, hrt.prefab.fx.Emitter) == null) {
 			// Don't extract animations for children of Emitters
@@ -308,79 +260,6 @@ class FX extends hrt.prefab.Library {
 			anims.push(anim);
 	}
 
-	public function refreshObjectAnims(ctx: Context) {
-		var fxanim = Std.downcast(ctx.local3d, FXAnimation);
-		fxanim.objects = [];
-		getObjAnimations(ctx, this, fxanim.objects);
-	}
-
-	public static function makeShaderParams(ctx: Context, shaderElt: hrt.prefab.Shader) {
-		shaderElt.loadShaderDef(ctx);
-		var shaderDef = shaderElt.shaderDef;
-		if(shaderDef == null)
-			return null;
-
-		var ret : ShaderParams = [];
-
-		for(v in shaderDef.shader.data.vars) {
-			if(v.kind != Param)
-				continue;
-
-			var prop = Reflect.field(shaderElt.props, v.name);
-			if(prop == null)
-				prop = hrt.prefab.Shader.getDefault(v.type);
-
-			var curves = Curve.getCurves(shaderElt, v.name);
-			if(curves == null || curves.length == 0)
-				continue;
-
-			switch(v.type) {
-				case TVec(_, VFloat) :
-					var isColor = v.name.toLowerCase().indexOf("color") >= 0;
-					var val = isColor ? Curve.getColorValue(curves) : Curve.getVectorValue(curves);
-					ret.push({
-						def: v,
-						value: val
-					});
-
-				default:
-					var base = 1.0;
-					if(Std.is(prop, Float) || Std.is(prop, Int))
-						base = cast prop;
-					var curve = Curve.getCurve(shaderElt, v.name);
-					var val = Value.VConst(base);
-					if(curve != null)
-						val = Value.VCurveScale(curve, base);
-					ret.push({
-						def: v,
-						value: val
-					});
-			}
-		}
-
-		return ret;
-	}
-
-	public static function getShaderAnims(ctx: Context, elt: PrefabElement, anims: Array<ShaderAnimation>) {
-		if(Std.downcast(elt, hrt.prefab.fx.Emitter) == null) {
-			for(c in elt.children) {
-				getShaderAnims(ctx, c, anims);
-			}
-		}
-
-		var shader = elt.to(hrt.prefab.Shader);
-		if(shader == null)
-			return;
-
-		for(shCtx in ctx.shared.getContexts(elt)) {
-			if(shCtx.custom == null) continue;
-			var anim: ShaderAnimation = new ShaderAnimation(new hxd.Rand(0));
-			anim.shader = shCtx.custom;
-			anim.params = makeShaderParams(ctx, shader);
-			anims.push(anim);
-		}
-	}
-
 	function getEmitters(ctx: Context, elt: PrefabElement, emitters: Array<hrt.prefab.fx.Emitter.EmitterObject>) {
 		var em = Std.downcast(elt, hrt.prefab.fx.Emitter);
 		if(em != null)  {
@@ -396,36 +275,10 @@ class FX extends hrt.prefab.Library {
 		}
 	}
 
-	function getFXRoot( ctx : Context, elt : PrefabElement ) : PrefabElement {
-		if( elt.name == "FXRoot" )
-			return elt;
-		else {
-			for(c in elt.children) {
-				var elt = getFXRoot(ctx, c);
-				if(elt != null) return elt;
-			}
-		}
-		return null;
-	}
-
-	function getConstraints( ctx : Context, elt : PrefabElement, constraints : Array<hrt.prefab.Constraint>){
-		var co = Std.downcast(elt, hrt.prefab.Constraint);
-		if(co != null)
-			constraints.push(co);
-		else
-			for(c in elt.children)
-				getConstraints(ctx, c, constraints);
-	}
-
-	function createInstance(parent: h3d.scene.Object) : FXAnimation {
-		return new FXAnimation(parent);
-	}
-
 	override function make( ctx : Context ) : Context {
 		ctx = ctx.clone(this);
 		var fxanim = createInstance(ctx.local3d);
 		fxanim.duration = duration;
-		fxanim.loopAnims = loopAnims;
 		fxanim.cullingRadius = cullingRadius;
 		ctx.local3d = fxanim;
 		fxanim.playSpeed = 1.0;
@@ -446,7 +299,7 @@ class FX extends hrt.prefab.Library {
 		#end
 
 		getObjAnimations(ctx, this, fxanim.objects);
-		getShaderAnims(ctx, this, fxanim.shaderAnims);
+		BaseFX.getShaderAnims(ctx, this, fxanim.shaderAnims);
 		getEmitters(ctx, this, fxanim.emitters);
 
 		if(scriptCode != null && scriptCode != ""){
@@ -462,10 +315,13 @@ class FX extends hrt.prefab.Library {
 		super.updateInstance(ctx, null);
 		var fxanim = Std.downcast(ctx.local3d, FXAnimation);
 		fxanim.duration = duration;
-		fxanim.loopAnims = loopAnims;
 		fxanim.cullingRadius = cullingRadius;
 	}
 
+	function createInstance(parent: h3d.scene.Object) : FXAnimation {
+		return new FXAnimation(parent);
+	}
+
 	#if editor
 	override function edit( ctx : EditContext ) {
 		var props = new hide.Element('
@@ -473,7 +329,6 @@ class FX extends hrt.prefab.Library {
 				<dl>
 					<dt>Duration</dt><dd><input type="number" value="0" field="duration"/></dd>
 					<dt>Culling radius</dt><dd><input type="number" field="cullingRadius"/></dd>
-					<dt>Loop Anims</dt><dd><input type="checkbox" field="loopAnims"/></dd>
 				</dl>
 			</div>');
 		ctx.properties.add(props, this, function(pname) {

+ 257 - 0
hrt/prefab/fx/FX2D.hx

@@ -0,0 +1,257 @@
+package hrt.prefab.fx;
+import hrt.prefab.Curve;
+import hrt.prefab.Prefab as PrefabElement;
+import hrt.prefab.fx.BaseFX.ObjectAnimation;
+import hrt.prefab.fx.BaseFX.ShaderAnimation;
+
+class FX2DAnimation extends h2d.Object {
+
+	public var prefab : hrt.prefab.Prefab;
+	public var onEnd : Void -> Void;
+	public var followVisibility : h3d.scene.Object;
+
+	public var playSpeed : Float;
+	public var localTime : Float = 0.0;
+	public var duration : Float;
+
+	public var loop : Bool;
+	public var objects: Array<ObjectAnimation> = [];
+	public var shaderAnims : Array<ShaderAnimation> = [];
+	public var constraints : Array<hrt.prefab.Constraint> = [];
+
+	var evaluator : Evaluator;
+	var random : hxd.Rand;
+
+	public function new(?parent) {
+		super(parent);
+		random = new hxd.Rand(Std.random(0xFFFFFF));
+		evaluator = new Evaluator(random);
+		name = "FX2DAnimation";
+		setTime(0);
+	}
+
+	public function setRandSeed(seed: Int) {
+		random.init(seed);
+	}
+
+	public dynamic function customVisibility(self: FX2DAnimation) : Bool {
+		return true;
+	}
+
+	override function sync( ctx : h2d.RenderContext ) {
+		var visiblity : Bool = true;
+		if(followVisibility != null)
+			visiblity = visiblity && followVisibility.visible;
+		if(customVisibility != null)
+			visiblity = visiblity && customVisibility(this);
+
+		this.visible = visiblity;
+
+		super.sync(ctx);
+	}
+
+	public function setTime( time : Float ) {
+		this.localTime = time;
+		
+		if (loop)
+			time = this.localTime % duration;
+		for(anim in objects) {
+			if(anim.scale != null) {
+				var scale = evaluator.getVector(anim.scale, time);
+				anim.obj2d.scaleX = scale.x;
+				anim.obj2d.scaleY = scale.y;
+			}
+
+			if(anim.rotation != null) {
+				var rotation = evaluator.getVector(anim.rotation, time);
+				anim.obj2d.rotation = rotation.x;
+			}
+
+			if(anim.position != null) {
+				var pos = evaluator.getVector(anim.position, time);
+				anim.obj2d.x = pos.x;
+				anim.obj2d.y = pos.y;
+			}
+
+			if(anim.visibility != null)
+				anim.obj2d.visible = anim.elt2d.visible && evaluator.getFloat(anim.visibility, time) > 0.5;
+
+			if(anim.color != null) {
+				switch(anim.color) {
+					case VCurve(a):
+						anim.obj2d.alpha = evaluator.getFloat(anim.color, time);
+					default:
+						var drawable = Std.downcast(anim.obj2d, h2d.Drawable);
+						if (drawable != null)
+							drawable.color = evaluator.getVector(anim.color, time);
+				}
+			}
+
+			if(anim.events != null) {
+				for(evt in anim.events) {
+					evt.setTime(time - evt.evt.time);
+				}
+			}
+		}
+
+		for(anim in shaderAnims) {
+			anim.setTime(time);
+		}
+	}
+}
+
+class FX2D extends BaseFX {
+	
+	var loop : Bool = false;
+
+	public function new() {
+		super();
+		type = "fx2d";
+	}
+
+	override function save() {
+		var obj : Dynamic = super.save();
+		obj.loop = loop;
+		return obj;
+	}
+
+	override function load( obj : Dynamic ) {
+		super.load(obj);
+		loop = obj.loop;
+	}
+
+	override public function refreshObjectAnims(ctx: Context) {
+		var fxanim = Std.downcast(ctx.local2d, FX2DAnimation);
+		fxanim.objects = [];
+		getObjAnimations(ctx, this, fxanim.objects);
+	}
+
+	static function getObjAnimations(ctx:Context, elt: PrefabElement, anims: Array<ObjectAnimation>) {
+		for(c in elt.children) {
+			getObjAnimations(ctx, c, anims);
+		}
+		
+		var obj2d = elt.to(hrt.prefab.Object2D);
+		if(obj2d == null)
+			return;
+
+		// TODO: Support references?
+		var objCtx = ctx.shared.contexts.get(elt);
+		if(objCtx == null || objCtx.local2d == null)
+			return;
+
+		var anyFound = false;
+
+		function makeVal(name, def) : Value {
+			var c = Curve.getCurve(elt, name);
+			if(c != null)
+				anyFound = true;
+			return c != null ? VCurve(c) : def;
+		}
+
+		function makeVector(name: String, defVal: Float, uniform: Bool=true, scale: Float=1.0) : Value {
+			var curves = Curve.getCurves(elt, name);
+			if(curves == null || curves.length == 0)
+				return null;
+
+			anyFound = true;
+
+			if(uniform && curves.length == 1 && curves[0].name == name)
+				return scale != 1.0 ? VCurveScale(curves[0], scale) : VCurve(curves[0]);
+
+			return Curve.getVectorValue(curves, defVal, scale);
+		}
+
+		function makeColor(name: String) {
+			var curves = Curve.getCurves(elt, name);
+			if(curves == null || curves.length == 0)
+				return null;
+
+			anyFound = true;
+			return Curve.getColorValue(curves);
+		}
+
+		var anim : ObjectAnimation = {
+			elt2d: obj2d,
+			obj2d: objCtx.local2d,
+			events: null,
+			position: makeVector("position", 0.0),
+			scale: makeVector("scale", 1.0, true),
+			rotation: makeVector("rotation", 0.0, 360.0),
+			color: makeColor("color"),
+			visibility: makeVal("visibility", null),
+		};
+
+		for(evt in elt.getAll(Event)) {
+			var eventObj = evt.prepare(objCtx);
+			if(eventObj == null) continue;
+			if(anim.events == null) anim.events = [];
+			anim.events.push(eventObj);
+			anyFound = true;
+		}
+
+		if(anyFound)
+			anims.push(anim);
+	}
+
+	override function make( ctx : Context ) : Context {
+		ctx = ctx.clone(this);
+		var fxanim = createInstance(ctx.local2d);
+		fxanim.duration = duration;
+		fxanim.loop = loop;
+		ctx.local2d = fxanim;
+		fxanim.playSpeed = 1.0;
+
+		#if editor
+		super.make(ctx);
+		#else
+		var root = getFXRoot(ctx, this);
+		if(root != null){
+			for( c in root.children ){
+				var co = Std.downcast(c , Constraint);
+				if(co == null) c.make(ctx);
+			}
+			getConstraints(ctx, root, fxanim.constraints);
+		}
+		else
+			super.make(ctx);
+		#end
+
+		getObjAnimations(ctx, this, fxanim.objects);
+		BaseFX.getShaderAnims(ctx, this, fxanim.shaderAnims);
+
+		return ctx;
+	}
+
+	override function updateInstance( ctx: Context, ?propName : String ) {
+		super.updateInstance(ctx, null);
+		var fxanim = Std.downcast(ctx.local2d, FX2DAnimation);
+		fxanim.duration = duration;
+		fxanim.loop = loop;
+	}
+
+	function createInstance(parent: h2d.Object) : FX2DAnimation {
+		return new FX2DAnimation(parent);
+	}
+
+	#if editor
+	override function edit( ctx : EditContext ) {
+		var props = new hide.Element('
+			<div class="group" name="FX2D Scene">
+				<dl>
+					<dt>Duration</dt><dd><input type="number" value="0" field="duration"/></dd>
+					<dt>Loop</dt><dd><input type="checkbox" field="loop"/></dd>
+				</dl>
+			</div>');
+		ctx.properties.add(props, this, function(pname) {
+			ctx.onChange(this, pname);
+		});
+	}
+
+	override function getHideProps() : HideProps {
+		return { icon : "cube", name : "FX2D", allowParent: _ -> false};
+	}
+	#end
+
+	static var _ = Library.register("fx2d", FX2D, "fx2d");
+}

+ 4 - 2
hrt/prefab/fx/FXScript.hx

@@ -1,5 +1,7 @@
 package hrt.prefab.fx;
 
+import hrt.prefab.fx.FX.FXAnimation;
+
 typedef Argument = { name : String, ?value : FxAst };
 
 enum FxAst {
@@ -35,12 +37,12 @@ class FXScript {
 	public var myVars : Map<String, FXVar> = [];
 	public var params : Array<FXParam> = [];
 
-	var fx : FX.FXAnimation;
+	var fx : FXAnimation;
 	var ast : FxAst;
 	var initAst : FxAst;
 	var updateAst : FxAst;
 
-	public function new( fx : FX.FXAnimation ){
+	public function new( fx : FXAnimation ){
 		this.fx = fx;
 	}
 

+ 1 - 1
hrt/shgraph/ShaderFunction.hx

@@ -18,7 +18,7 @@ class ShaderFunction extends ShaderNode {
 
 		for (k in getInputInfoKeys()) {
 			if (getInputInfo(k).hasProperty && getInput(k) == null) {
-				var value = Reflect.field(this, "prop_"+k);
+				var value : Dynamic = Reflect.field(this, "prop_"+k);
 				if (value == null)
 					value = 0;
 				args.push({ name: k, type: TFloat });