2
0
Эх сурвалжийг харах

[fxparams] Implemented first draft for fx parameters

Clement Espeute 1 жил өмнө
parent
commit
b3188e7db3

+ 32 - 0
bin/style.css

@@ -3124,6 +3124,38 @@ div.gradient-box {
   margin-top: 15px;
   margin-top: 15px;
   margin-bottom: 15px;
   margin-bottom: 15px;
 }
 }
+.hover-parent .hover-reveal {
+  visibility: hidden;
+}
+.hover-parent:hover {
+  background-color: rgba(255, 255, 255, 0.05);
+}
+.hover-parent:hover .hover-reveal {
+  visibility: visible;
+}
+.flex-props input {
+  min-width: 0 !important;
+  width: 100% !important;
+  min-height: 0 !important;
+  overflow: hidden;
+  flex: 1;
+}
+.flex-props .hide-range {
+  display: flex;
+  flex: 1;
+}
+.flex-props .hide-range [type="range"] {
+  flex: 3;
+}
+.flex-props .flex {
+  display: inline-flex;
+}
+.flex-props .fill {
+  flex: 1;
+}
+.fx-params dt {
+  text-transform: none;
+}
 .settings {
 .settings {
   width: 100%;
   width: 100%;
   height: 100%;
   height: 100%;

+ 46 - 0
bin/style.less

@@ -50,6 +50,8 @@ body {
 	border : 1px solid #C00;
 	border : 1px solid #C00;
 }
 }
 
 
+
+
 .error-panel {
 .error-panel {
 	position : absolute;
 	position : absolute;
 	width: 100%;
 	width: 100%;
@@ -3583,6 +3585,50 @@ div.gradient-box {
 	}
 	}
 }
 }
 
 
+.hover-parent {
+	.hover-reveal {
+		visibility: hidden;
+	}
+	&:hover {
+		.hover-reveal {
+			visibility: visible;
+		}
+		background-color: rgba(255,255,255,0.05);
+	}
+}
+
+.flex-props {
+	input{
+		min-width: 0 !important;
+		width: 100% !important;
+		min-height: 0 !important;
+		overflow: hidden;
+		flex: 1;
+	}
+
+	.hide-range {
+		display: flex;
+		flex: 1;
+		[type="range"] {
+			flex: 3;
+		}
+	}
+
+	.flex {
+		display: inline-flex;
+	}
+
+	.fill {
+		flex: 1;
+	}
+}
+
+.fx-params {
+	dt {
+		text-transform: none;
+	}
+}
+
 .settings {
 .settings {
 	width: 100%;
 	width: 100%;
 	height: 100%;
 	height: 100%;

+ 1 - 2
hide/comp/ColorPicker.hx

@@ -697,8 +697,7 @@ class SliderGroup extends Component {
 	function get_value() {
 	function get_value() {
 		var color = new Color();
 		var color = new Color();
 		colorMode.valueToARGB(workValue, color);
 		colorMode.valueToARGB(workValue, color);
-		return if (!canEditAlpha) (color.r << 16) + (color.g << 8) + color.b
-		else (color.r << 16) + (color.g << 8) + (color.b << 0) + (color.a << 24);
+		return color.toInt(canEditAlpha);
 	};
 	};
 
 
 	public function addSlider(slider : ColorSlider) : ColorSlider {
 	public function addSlider(slider : ColorSlider) : ColorSlider {

+ 73 - 0
hide/comp/ContentEditable.hx

@@ -0,0 +1,73 @@
+package hide.comp;
+
+class ContentEditable extends Component {
+	public var value(get, set) : String;
+	public var spellcheck(never, set) : Bool;
+
+	var html : js.html.Element = null;
+
+	public function new(?parent : Element, ?element : Element) {
+		if (element == null) {
+			element = new Element("<div contenteditable></div>");
+		}
+		super(parent, element);
+
+		html = element.get(0);
+
+		var wasEdited = false;
+
+		html.onfocus = function() {
+			var range = js.Browser.document.createRange();
+			range.selectNodeContents(html);
+			var sel = js.Browser.window.getSelection();
+			sel.removeAllRanges();
+			sel.addRange(range);
+		}
+		html.onkeydown = function(e: js.html.KeyboardEvent) {
+			if (e.keyCode == 13) {
+				html.blur();
+			}
+			e.stopPropagation();
+		}
+		html.oninput = function(e) {
+			if (!wasEdited) {
+				wasEdited = true;
+			}
+		}
+		html.onkeyup = function(e: js.html.KeyboardEvent) {
+			e.stopPropagation();
+		}
+		html.onmousedown = function(e: js.html.PointerEvent) {
+			e.stopPropagation();
+		}
+		html.onmousemove = function(e: js.html.PointerEvent) {
+			e.stopPropagation();
+		}
+		html.onmouseup = function(e: js.html.PointerEvent) {
+			e.stopPropagation();
+		}
+
+		html.onblur = function() {
+			if (js.Browser.window.getSelection != null) {js.Browser.window.getSelection().removeAllRanges();}
+			if (wasEdited) {
+				onChange(get_value());
+				wasEdited = false;
+			}
+		}
+	}
+
+	function set_value(v: String) {
+		return html.innerText = v;
+	}
+
+	function set_spellcheck(v: Bool) {
+		html.setAttribute("spellcheck", v ? "true" : "false");
+		return v;
+	}
+
+	function get_value() {
+		return html.innerText;
+	}
+
+	public dynamic function onChange(v: String) {};
+}

+ 13 - 1
hide/comp/CurveEditor.hx

@@ -493,6 +493,7 @@ class CurveEditor extends hide.comp.Component {
 	public var lockViewY = false;
 	public var lockViewY = false;
 	public var lockKeyX = false;
 	public var lockKeyX = false;
 	public var maxLength = 0.0;
 	public var maxLength = 0.0;
+	public var evaluator : hrt.prefab.fx.Evaluator;
 
 
 	public var components : Array<CurveEditorComponent> = [];
 	public var components : Array<CurveEditorComponent> = [];
 	public var componentsGroup : Element;
 	public var componentsGroup : Element;
@@ -542,6 +543,8 @@ class CurveEditor extends hide.comp.Component {
 		tlGroup = svg.group(root, "tlgroup");
 		tlGroup = svg.group(root, "tlgroup");
 		markersGroup = svg.group(root, "markers").css({'pointer-events':'none'});
 		markersGroup = svg.group(root, "markers").css({'pointer-events':'none'});
 
 
+		evaluator = new hrt.prefab.fx.Evaluator([]);
+
 		var sMin = 0.0;
 		var sMin = 0.0;
 		var sMax = 0.0;
 		var sMax = 0.0;
 		tlGroup.mousedown(function(e) {
 		tlGroup.mousedown(function(e) {
@@ -1309,8 +1312,17 @@ class CurveEditor extends hide.comp.Component {
 					svg.make(curveGroup, "path", {d: lines.join("")});
 					svg.make(curveGroup, "path", {d: lines.join("")});
 				}
 				}
 				else {
 				else {
+					var pts = [];
+
 					// Basic value of xScale is 200
 					// Basic value of xScale is 200
-					var pts = curve.sample(cast Math.min(5000, 500 * cast (xScale / 200.0)));
+					var num : Int = Std.int(Math.min(5000, 500 * cast (xScale / 200.0)));
+					pts.resize(num);
+					var v = curve.makeVal();
+					if (v == null) throw "wtf";
+					var duration = curve.duration;
+					for (i in 0...num) {
+						pts[i] = evaluator.getFloat(v, duration * i/(num-1));
+					}
 					var poly = [];
 					var poly = [];
 
 
 					for(i in 0...pts.length) {
 					for(i in 0...pts.length) {

+ 13 - 2
hide/view/FXEditor.hx

@@ -668,9 +668,15 @@ class FXEditor extends hide.view.FileView {
 			rebuildAnimPanel();
 			rebuildAnimPanel();
 		}
 		}
 
 
-		if (pname == "blendFactor") {
-			if (this.curveEditor != null)
+		if (pname == "parameters") {
+			if (this.curveEditor != null) {
+				var fx3d = Std.downcast(data, hrt.prefab.fx.FX);
+				if (fx3d != null) {
+					var params = fx3d.parameters;
+					this.curveEditor.evaluator.setAllParameters(params);
+				}
 				this.curveEditor.refreshGraph();
 				this.curveEditor.refreshGraph();
+			}
 		}
 		}
 
 
 	}
 	}
@@ -784,6 +790,11 @@ class FXEditor extends hide.view.FileView {
 			});
 			});
 		}
 		}
 
 
+		var fx3d = Std.downcast(data, hrt.prefab.fx.FX);
+		if (fx3d != null) {
+			var params = fx3d.parameters;
+			this.curveEditor.evaluator.setAllParameters(params);
+		}
 		this.curveEditor.refreshTimeline(previousTime);
 		this.curveEditor.refreshTimeline(previousTime);
 		this.curveEditor.refresh();
 		this.curveEditor.refresh();
 	}
 	}

+ 7 - 45
hide/view/shadereditor/Box.hx

@@ -204,54 +204,16 @@ class Box {
 				var fo = editor.editor.foreignObject(element, 7, 2, 0, HEADER_HEIGHT-4);
 				var fo = editor.editor.foreignObject(element, 7, 2, 0, HEADER_HEIGHT-4);
 				fo.get(0).id = "commentTitle";
 				fo.get(0).id = "commentTitle";
 				var commentTitle = new Element("<span contenteditable spellcheck='false'>Comment</span>").addClass("comment-title").appendTo(fo);
 				var commentTitle = new Element("<span contenteditable spellcheck='false'>Comment</span>").addClass("comment-title").appendTo(fo);
-				var html : js.html.SpanElement = cast commentTitle.get(0);
-				if (comment.comment.length > 0) {
-					html.innerText = comment.comment;
-				}
 
 
-				var wasEdited = false;
 				var shaderEditor : ShaderEditor = cast editor;
 				var shaderEditor : ShaderEditor = cast editor;
 
 
-				html.onfocus = function() {
-					var range = js.Browser.document.createRange();
-					range.selectNodeContents(html);
-					var sel = js.Browser.window.getSelection();
-					sel.removeAllRanges();
-					sel.addRange(range);
-				}
-				html.onkeydown = function(e: js.html.KeyboardEvent) {
-					if (e.keyCode == 13) {
-						html.blur();
-					}
-					e.stopPropagation();
-				}
-				html.oninput = function(e) {
-					if (!wasEdited) {
-						shaderEditor.beforeChange();
-						wasEdited = true;
-					}
-				}
-				html.onkeyup = function(e: js.html.KeyboardEvent) {
-					e.stopPropagation();
-				}
-				html.onmousedown = function(e: js.html.PointerEvent) {
-					e.stopPropagation();
-				}
-				html.onmousemove = function(e: js.html.PointerEvent) {
-					e.stopPropagation();
-				}
-				html.onmouseup = function(e: js.html.PointerEvent) {
-					e.stopPropagation();
-				}
-
-				html.onblur = function() {
-					if (js.Browser.window.getSelection != null) {js.Browser.window.getSelection().removeAllRanges();}
-					comment.comment = html.innerText;
-					if (wasEdited) {
-						shaderEditor.afterChange();
-						wasEdited = false;
-					}
-				}
+				var editable = new hide.comp.ContentEditable(commentTitle);
+				editable.value = comment.comment;
+				editable.onChange = function(v: String) {
+					shaderEditor.beforeChange();
+					comment.comment = v;
+					shaderEditor.afterChange();
+				};
 			}
 			}
 			else {
 			else {
 				editor.editor.text(element, 7, HEADER_HEIGHT-6, className).addClass("title-box");
 				editor.editor.text(element, 7, HEADER_HEIGHT-6, className).addClass("title-box");

+ 5 - 0
hrt/impl/ColorSpace.hx

@@ -30,6 +30,11 @@ class Color {
 			withAlpha ? (rgb >> 24) & 0xFF : 255
 			withAlpha ? (rgb >> 24) & 0xFF : 255
 		);
 		);
 	}
 	}
+
+	inline public function toInt(includeAlpha: Bool) : Int {
+		return if (!includeAlpha) (r << 16) + (g << 8) + b
+		else (r << 16) + (g << 8) + (b << 0) + (a << 24);
+	}
 }
 }
 
 
 class ColorSpace {
 class ColorSpace {

+ 60 - 22
hrt/prefab/Curve.hx

@@ -52,8 +52,8 @@ class Curve extends Prefab {
 	@:s public var previewKeys : CurveKeys = [];
 	@:s public var previewKeys : CurveKeys = [];
 
 
 	@:s public var blendMode : CurveBlendMode = None;
 	@:s public var blendMode : CurveBlendMode = None;
-	@:s public var blendFactor : Float = 0;
 	@:s public var loop : Bool = false;
 	@:s public var loop : Bool = false;
+	@:s public var blendVariable : String = null;
 
 
 	public var maxTime : Float = 5000.;
 	public var maxTime : Float = 5000.;
 	public var duration(get, never): Float;
 	public var duration(get, never): Float;
@@ -198,15 +198,21 @@ class Curve extends Prefab {
 		return ret;
 		return ret;
 	}
 	}
 
 
-	public function getVal(time: Float) : Float {
-		if (blendMode == CurveBlendMode.Blend) {
-			var c1 = Std.downcast(this.children[0], Curve);
-			var c2 = Std.downcast(this.children[1], Curve);
-			var a = c1.getVal(time);
-			var b = c2.getVal(time);
-			return a + (b - a) * blendFactor;
+	public function makeVal() : Value {
+		switch(blendMode) {
+			case None:
+				return VCurve(this);
+			case Blend:
+				return VBlend(Std.downcast(this.children[0], Curve).makeVal(), Std.downcast(this.children[1], Curve).makeVal(), blendVariable);
+			case RandomBlend:
+				return VCurve(this);
+			default:
+				throw "what";
 		}
 		}
+		throw "unrechable";
+	}
 
 
+	public function getVal(time: Float) : Float {
 		switch(keys.length) {
 		switch(keys.length) {
 			case 0: return 0;
 			case 0: return 0;
 			case 1: return keys[0].value;
 			case 1: return keys[0].value;
@@ -327,19 +333,54 @@ class Curve extends Prefab {
 	override function edit( ctx : EditContext ) {
 	override function edit( ctx : EditContext ) {
 		super.edit(ctx);
 		super.edit(ctx);
 
 
-		ctx.properties.add(new hide.Element('
+		var props = new hide.Element('
 		<div class="group" name="Parameters">
 		<div class="group" name="Parameters">
 			<dl>
 			<dl>
 				<dt>Loop curve</dt><dd><input type="checkbox" field="loop"/></dd>
 				<dt>Loop curve</dt><dd><input type="checkbox" field="loop"/></dd>
 				<dt>Blend Mode</dt><dd>
 				<dt>Blend Mode</dt><dd>
-					<select class="blendmode-selector" field="blendMode">
+					<select class="blendmode-selector" id="blendMode">
 						<option value="0">None</option>
 						<option value="0">None</option>
 						<option value="1">Blend curve</option>
 						<option value="1">Blend curve</option>
 						<option value="2">Random between two curves</option>
 						<option value="2">Random between two curves</option>
 					</select>
 					</select>
 				</dd>
 				</dd>
+				<div id="parameter">
+					<dt>Parameter</dt><dd>
+					<select field="blendVariable"></select>
+					</dd>
+				</div>
 			</dl>
 			</dl>
-		</div>'), this, function(pname) {
+		</div>');
+
+		var blendModeSelect = props.find('#blendMode');
+
+		var refreshBlend = function() {
+			var parameter = props.find('#parameter');
+			var selecta = props.find('[field="blendVariable"]');
+			if (blendMode != Blend) {
+				parameter.hide();
+			}
+			else {
+				parameter.show();
+				selecta.empty();
+				var root = Std.downcast(getRoot(false), hrt.prefab.fx.FX);
+				for (p in root.parameters) {
+					selecta.append(new hide.Element('<option value="${p.name}">${p.name}</option>'));
+				}
+			}
+		}
+
+		refreshBlend();
+
+		blendModeSelect.change((_) -> {
+			var val = blendModeSelect.val();
+			blendMode = cast Std.parseInt(val);
+			refreshBlend();
+			ctx.onChange(this, "blendMode");
+		});
+
+		ctx.properties.add(props, this, function(pname) {
+			refreshBlend();
 			ctx.onChange(this, pname);
 			ctx.onChange(this, pname);
 		});
 		});
 
 
@@ -417,9 +458,6 @@ class Curve extends Prefab {
 			if (c == null)
 			if (c == null)
 				return VConst(defVal);
 				return VConst(defVal);
 
 
-			if (c.blendMode == CurveBlendMode.Blend)
-				return VBlendCurve(c, blendFactor);
-
 			if (scale != 1.0)
 			if (scale != 1.0)
 				return VCurveScale(c, scale);
 				return VCurveScale(c, scale);
 
 
@@ -448,10 +486,10 @@ class Curve extends Prefab {
 
 
 		if(h != null || s != null || l != null) {
 		if(h != null || s != null || l != null) {
 			return VHsl(
 			return VHsl(
-				h != null ? VCurve(h) : VConst(0.0),
-				s != null ? VCurve(s) : VConst(1.0),
-				l != null ? VCurve(l) : VConst(1.0),
-				a != null ? VCurve(a) : VConst(1.0));
+				h != null ? h.makeVal() : VConst(0.0),
+				s != null ? s.makeVal() : VConst(1.0),
+				l != null ? l.makeVal() : VConst(1.0),
+				a != null ? a.makeVal() : VConst(1.0));
 		}
 		}
 
 
 		if(a != null && r == null && g == null && b == null)
 		if(a != null && r == null && g == null && b == null)
@@ -461,10 +499,10 @@ class Curve extends Prefab {
 			return VOne; // White by default
 			return VOne; // White by default
 
 
 		return VVector(
 		return VVector(
-			r != null ? VCurve(r) : VConst(1.0),
-			g != null ? VCurve(g) : VConst(1.0),
-			b != null ? VCurve(b) : VConst(1.0),
-			a != null ? VCurve(a) : VConst(1.0));
+			r != null ? r.makeVal() : VConst(1.0),
+			g != null ? g.makeVal() : VConst(1.0),
+			b != null ? b.makeVal() : VConst(1.0),
+			a != null ? a.makeVal() : VConst(1.0));
 	}
 	}
 
 
 	static var _ = Prefab.register("curve", Curve);
 	static var _ = Prefab.register("curve", Curve);

+ 12 - 3
hrt/prefab/fx/Evaluator.hx

@@ -2,6 +2,7 @@ package hrt.prefab.fx;
 
 
 class Evaluator {
 class Evaluator {
 	var randValues : Array<Float>;
 	var randValues : Array<Float>;
+	public var parameters: Map<String, Float> = [];
 	var stride : Int;
 	var stride : Int;
 
 
 	public function new(?randValues: Array<Float>, stride: Int=0) {
 	public function new(?randValues: Array<Float>, stride: Int=0) {
@@ -14,6 +15,13 @@ class Evaluator {
 		return randValues[i];
 		return randValues[i];
 	}
 	}
 
 
+	public function setAllParameters(params: Array<hrt.prefab.fx.FX.Parameter>) {
+		parameters.clear();
+		for (p in params) {
+			parameters[p.name] = p.def;
+		}
+	}
+
 	public function getFloat(pidx: Int=0, val: Value, time: Float) : Float {
 	public function getFloat(pidx: Int=0, val: Value, time: Float) : Float {
 		if(val == null)
 		if(val == null)
 			return 0.0;
 			return 0.0;
@@ -21,9 +29,10 @@ class Evaluator {
 			case VZero: return 0.0;
 			case VZero: return 0.0;
 			case VOne: return 1.0;
 			case VOne: return 1.0;
 			case VConst(v): return v;
 			case VConst(v): return v;
-			case VCurve(c): return c.getVal(time);
-			case VBlendCurve(c, factor):
-				return c.getVal(time);
+			case VBlend(a,b,v):
+				var blend = parameters[v] ?? 0.0;
+				return hxd.Math.lerp(getFloat(pidx, a, time), getFloat(pidx, b, time), blend);
+			case VCurve(c):  return c.getVal(time);
 			case VRandomBetweenCurves(ridx, c):
 			case VRandomBetweenCurves(ridx, c):
 				{
 				{
 					var c1 = Std.downcast(c.children[0], Curve);
 					var c1 = Std.downcast(c.children[0], Curve);

+ 176 - 12
hrt/prefab/fx/FX.hx

@@ -18,6 +18,7 @@ class FXAnimation extends h3d.scene.Object {
 	public var loop : Bool = false;
 	public var loop : Bool = false;
 	public var duration : Float;
 	public var duration : Float;
 
 
+
 	/** Enable automatic culling based on `cullingRadius` and `cullingDistance`. Will override `culled` on every sync. **/
 	/** Enable automatic culling based on `cullingRadius` and `cullingDistance`. Will override `culled` on every sync. **/
 	public var autoCull(default, set) = true;
 	public var autoCull(default, set) = true;
 	public var cullingRadius : Float;
 	public var cullingRadius : Float;
@@ -60,6 +61,7 @@ class FXAnimation extends h3d.scene.Object {
 		initConstraints(root != null ? root : def);
 		initConstraints(root != null ? root : def);
 
 
 		trails = findAll((p) -> Std.downcast(p, hrt.prefab.l3d.Trails.TrailObj));
 		trails = findAll((p) -> Std.downcast(p, hrt.prefab.l3d.Trails.TrailObj));
+		setParameters(def.parameters);
 	}
 	}
 
 
 	public function reset() {
 	public function reset() {
@@ -74,6 +76,13 @@ class FXAnimation extends h3d.scene.Object {
 		}
 		}
 	}
 	}
 
 
+	public function setParameters(params: Array<Parameter>) {
+		evaluator.parameters.clear();
+		for (p in params) {
+			evaluator.parameters[p.name] = p.def;
+		}
+	}
+
 	public function setRandSeed(seed: Int) {
 	public function setRandSeed(seed: Int) {
 		randSeed = seed;
 		randSeed = seed;
 		random.init(seed);
 		random.init(seed);
@@ -162,6 +171,7 @@ class FXAnimation extends h3d.scene.Object {
 	public function setTime( time : Float, fullSync=true ) {
 	public function setTime( time : Float, fullSync=true ) {
 		var dt = time - this.prevTime;
 		var dt = time - this.prevTime;
 		this.localTime = time;
 		this.localTime = time;
+
 		if(fullSync) {
 		if(fullSync) {
 			if(objAnims != null) {
 			if(objAnims != null) {
 				for(anim in objAnims) {
 				for(anim in objAnims) {
@@ -329,8 +339,7 @@ class FXAnimation extends h3d.scene.Object {
 
 
 			if (c == null)
 			if (c == null)
 				return def;
 				return def;
-
-			return c.blendMode == CurveBlendMode.Blend ? VBlendCurve(c, blendFactor) : VCurve(c);
+			return c.makeVal();
 		}
 		}
 
 
 		function makeVector(name: String, defVal: Float, uniform: Bool=true, scale: Float=1.0) : Value {
 		function makeVector(name: String, defVal: Float, uniform: Bool=true, scale: Float=1.0) : Value {
@@ -341,10 +350,7 @@ class FXAnimation extends h3d.scene.Object {
 			anyFound = true;
 			anyFound = true;
 
 
 			if(uniform && curves.length == 1 && curves[0].name == name) {
 			if(uniform && curves.length == 1 && curves[0].name == name) {
-				if (curves[0].blendMode == CurveBlendMode.Blend)
-					return VBlendCurve(curves[0], blendFactor);
-
-				return scale != 1.0 ? VCurveScale(curves[0], scale) : VCurve(curves[0]);
+				return scale != 1.0 ? VCurveScale(curves[0], scale) : curves[0].makeVal();
 			}
 			}
 
 
 			return Curve.getVectorValue(curves, defVal, scale, blendFactor);
 			return Curve.getVectorValue(curves, defVal, scale, blendFactor);
@@ -446,6 +452,16 @@ class FXAnimation extends h3d.scene.Object {
 	}
 	}
 }
 }
 
 
+enum abstract ParameterType(String) {
+	var TBlend;
+}
+typedef Parameter = {
+	var type: ParameterType;
+	var name: String;
+	var color: Int;
+	var def: Dynamic;
+};
+
 class FX extends Object3D implements BaseFX {
 class FX extends Object3D implements BaseFX {
 
 
 	@:s public var duration : Float;
 	@:s public var duration : Float;
@@ -455,6 +471,11 @@ class FX extends Object3D implements BaseFX {
 	@:s public var markers : Array<{t: Float}> = [];
 	@:s public var markers : Array<{t: Float}> = [];
 	@:c public var blendFactor : Float;
 	@:c public var blendFactor : Float;
 
 
+	@:s public var parameters : Array<Parameter>;
+
+	#if editor
+	static var identRegex = ~/^[A-Za-z_][A-Za-z0-9_]*$/;
+	#end
 
 
 	/*override function save(data : Dynamic) {
 	/*override function save(data : Dynamic) {
 		super.save(data);
 		super.save(data);
@@ -524,12 +545,7 @@ class FX extends Object3D implements BaseFX {
 		fxanim.cullingRadius = cullingRadius;
 		fxanim.cullingRadius = cullingRadius;
 		fxanim.blendFactor = blendFactor;
 		fxanim.blendFactor = blendFactor;
 
 
-		// Populate the value among blend curves
-		var curves = this.flatten(Curve);
-		for (curve in curves) {
-			if (curve.blendMode == CurveBlendMode.Blend)
-				curve.blendFactor = blendFactor;
-		}
+		fxanim.setParameters(parameters);
 	}
 	}
 
 
 	function createInstance(parent: h3d.scene.Object) : FXAnimation {
 	function createInstance(parent: h3d.scene.Object) : FXAnimation {
@@ -556,6 +572,154 @@ class FX extends Object3D implements BaseFX {
 		ctx.properties.add(props, this, function(pname) {
 		ctx.properties.add(props, this, function(pname) {
 			ctx.onChange(this, pname);
 			ctx.onChange(this, pname);
 		});
 		});
+
+		var param = new hide.Element('
+			<div class="group flex-props fx-params" name="Parameters">
+				<dl id="params">
+				</dl>
+				<dl>
+				<dt></dt><dd><input type="button" value="Add Parameter" id="addParamButton"/></dd>
+				</dl>
+			</div>
+		'
+		);
+
+		function isParameterNameUnique(name: String) {
+			for (p in parameters) {
+				if (p.name == name) return false;
+			}
+			return true;
+		}
+
+		function rebuildParameters() {
+			var params = param.find("#params");
+			(params.get(0):Dynamic).replaceChildren();
+			if (parameters != null) {
+				for (i => p in parameters) {
+					var line = new hide.Element('<div class="hover-parent"/>');
+					line.appendTo(params);
+					var elem = new hide.Element('<dt class="flex"><div id="color"></div><span id="name" contenteditable class="fill"></span></dt>').appendTo(line);
+					var editable = new hide.comp.ContentEditable(null, elem.find("#name"));
+					editable.value = p.name;
+					editable.spellcheck = false;
+					editable.onChange = function(v: String) {
+						var error = false;
+						if (!identRegex.match(v)) {
+							hide.Ide.inst.quickMessage('Parameter name "$v" is not a valid identifier');
+							error = true;
+						}
+						else if (!isParameterNameUnique(v)) {
+							hide.Ide.inst.quickMessage('Parameter "$v" already exists');
+							error = true;
+						}
+						if (error) {
+							editable.value = p.name;
+							return;
+						}
+
+						var old = p.name;
+						var fn = function(isUndo: Bool) {
+							p.name = isUndo ? old : v;
+							editable.value = p.name;
+							ctx.onChange(this, "parameters");
+						}
+						ctx.properties.undo.change(Custom(fn));
+						fn(false);
+					};
+
+					var colorPicker = new hide.comp.ColorPicker.ColorBox(null, elem.find("#color"), true, false);
+					colorPicker.value = p.color;
+					colorPicker.element.width(8);
+					colorPicker.element.height(16);
+					var lastColor = p.color;
+					colorPicker.onChange = function(temp: Bool) {
+						if (temp) {
+							p.color = colorPicker.value;
+							ctx.onChange(this, "parameters");
+						}
+						else {
+							var old = lastColor;
+							var current = colorPicker.value;
+							var fn = function(isUndo : Bool) {
+								p.color = isUndo ? old : current;
+								colorPicker.value = p.color;
+							}
+							ctx.properties.undo.change(Custom(fn));
+							fn(false);
+						}
+					}
+
+					var dd = new hide.Element('<dd class="flex">').appendTo(line);
+					var range = new hide.comp.Range(dd, new hide.Element('<input min="0" max="1" type="range"/>'));
+					var btn = new hide.Element('<div class="tb-group small hover-reveal"><div class="button2"><div class="icon ico ico-times"></div></div></div>').appendTo(dd);
+					btn.find(".button2").get(0).onclick = function(e) {
+						var fn = function(isUndo: Bool) {
+							if (!isUndo) {
+								parameters.splice(i, 1);
+							}
+							else {
+								parameters.insert(i, p);
+							}
+							rebuildParameters();
+							ctx.onChange(this, "parameters");
+						}
+						ctx.properties.undo.change(Custom(fn));
+						fn(false);
+					}
+
+					range.value = p.def;
+					var lastDef = p.def;
+					range.onChange = function(temp: Bool) {
+						if (temp) {
+							p.def = range.value;
+							ctx.onChange(this, "parameters");
+						}
+						else {
+							var old = lastDef;
+							var current = range.value;
+							lastDef = current;
+							var fn = function(isUndo: Bool) {
+								p.def = isUndo ? old : current;
+								range.value = p.def;
+								ctx.onChange(this, "parameters");
+							}
+							ctx.properties.undo.change(Custom(fn));
+							fn(false);
+						}
+					}
+				}
+			}
+		}
+
+
+
+		param.find("#addParamButton").get(0).onclick = (_) -> {
+
+			var color = hrt.impl.ColorSpace.HSLtoiRGB(new h3d.Vector4((parameters.length * 0.618033988749895) % 1.0, 0.75, 0.5), null).toInt(false);
+			var paramName = "newParam";
+			var i = 0;
+			while (!isParameterNameUnique(paramName)) {
+				i ++;
+				paramName = 'newParam$i';
+			}
+			var newElem = {type: TBlend, name: paramName, color: color, def: 0.0};
+			var fn = function(isUndo: Bool) {
+				if (!isUndo) {
+					parameters.push(newElem);
+				} else {
+					parameters.pop();
+				}
+				ctx.onChange(this, "parameters");
+				rebuildParameters();
+			}
+			ctx.properties.undo.change(Custom(fn));
+			fn(false);
+		};
+
+		ctx.properties.add(param);
+
+		rebuildParameters();
+
 	}
 	}
 
 
 	override function getHideProps() : hide.prefab.HideProps {
 	override function getHideProps() : hide.prefab.HideProps {

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

@@ -5,7 +5,7 @@ enum Value {
 	VOne;
 	VOne;
 	VConst(v: Float);
 	VConst(v: Float);
 	VCurve(c: Curve);
 	VCurve(c: Curve);
-	VBlendCurve(c: Curve, factor: Float);
+	VBlend(a: Value, b: Value, blendVar: String);
 	VRandomBetweenCurves(idx: Int, c: Curve);
 	VRandomBetweenCurves(idx: Int, c: Curve);
 	VCurveScale(c: Curve, scale: Float);
 	VCurveScale(c: Curve, scale: Float);
 	VRandom(idx: Int, scale: Value);
 	VRandom(idx: Int, scale: Value);