Procházet zdrojové kódy

ShaderGraph: Undo/Redo + Lerp node

Tom SPIRA před 6 roky
rodič
revize
85df714739

+ 126 - 64
hide/view/shadereditor/ShaderEditor.hx

@@ -38,6 +38,8 @@ class ShaderEditor extends hide.view.Graph {
 
 	var shaderGraph : ShaderGraph;
 
+	var lastSnapshot : haxe.Json;
+
 	var timerCompileShader : Timer;
 	var COMPILE_SHADER_DEBOUNCE : Int = 100;
 	var VIEW_VISIBLE_CHECK_TIMER : Int = 500;
@@ -136,18 +138,18 @@ class ShaderEditor extends hide.view.Graph {
 		});
 
 		parent.on("keydown", function(e) {
-
 			if (e.shiftKey && e.keyCode != 16) {
 				if (addMenu == null || !addMenu.is(":visible"))
 					openAddMenu();
 
 				return;
 			}
-			if (e.keyCode == 83 && e.ctrlKey) { // CTRL+S : save
-				shaderGraph.save();
-			}
 		});
 
+		keys = new hide.ui.Keys(element);
+		keys.register("undo", function() undo.undo());
+		keys.register("redo", function() undo.redo());
+
 		parent.on("contextmenu", function(e) {
 			var elements = [];
 
@@ -219,9 +221,14 @@ class ShaderEditor extends hide.view.Graph {
 
 		parametersList = element.find("#parametersList");
 
+		editorMatrix.on("click", "input, select", function(ev) {
+			beforeChange();
+		});
+
 		editorMatrix.on("change", "input, select", function(ev) {
 			try {
 				shaderGraph.nodeUpdated(ev.target.closest(".box").id);
+				afterChange();
 				launchCompileShader();
 			} catch (e : Dynamic) {
 				if (Std.is(e, ShaderException)) {
@@ -254,38 +261,13 @@ class ShaderEditor extends hide.view.Graph {
 			});
 		}
 
-		listOfBoxes = [];
-		listOfEdges = [];
-
-		updateMatrix();
-
 		new Element("svg").ready(function(e) {
-
-			for (node in shaderGraph.getNodes()) {
-				var paramNode = Std.instance(node.instance, ShaderParam);
-				if (paramNode != null) {
-					var paramShader = shaderGraph.getParameter(paramNode.parameterId);
-					paramNode.setName(paramShader.name);
-					setDisplayValue(paramNode, paramShader.type, paramShader.defaultValue);
-					shaderGraph.nodeUpdated(paramNode.id);
-					addBox(new Point(node.x, node.y), ShaderParam, paramNode);
-				} else {
-					addBox(new Point(node.x, node.y), std.Type.getClass(node.instance), node.instance);
-				}
-			}
-
-			new Element(".nodes").ready(function(e) {
-				if (IsVisible()) {
-					centerView();
-					generateEdges();
-				}
-			});
-
-
-			for (p in shaderGraph.parametersAvailable) {
-				addParameter(p.id, p.name, p.type, p.defaultValue);
+			refreshShaderGraph();
+			if (IsVisible()) {
+				centerView();
 			}
 		});
+
 	}
 
 	override function save() {
@@ -328,6 +310,54 @@ class ShaderEditor extends hide.view.Graph {
 		}
 	}
 
+	function refreshShaderGraph(readyEvent : Bool = true) {
+
+		listOfBoxes = [];
+		listOfEdges = [];
+
+		var saveToggleParams = new Map<Int, Bool>();
+		for (pElt in parametersList.find(".parameter").elements()) {
+			saveToggleParams.set(Std.parseInt(pElt.get()[0].id.split("_")[1]), pElt.find(".content").css("display") != "none");
+		}
+		parametersList.empty();
+		editorMatrix.empty();
+
+		updateMatrix();
+
+		for (node in shaderGraph.getNodes()) {
+			var paramNode = Std.instance(node.instance, ShaderParam);
+			if (paramNode != null) {
+				var paramShader = shaderGraph.getParameter(paramNode.parameterId);
+				paramNode.setName(paramShader.name);
+				setDisplayValue(paramNode, paramShader.type, paramShader.defaultValue);
+				shaderGraph.nodeUpdated(paramNode.id);
+				addBox(new Point(node.x, node.y), ShaderParam, paramNode);
+			} else {
+				addBox(new Point(node.x, node.y), std.Type.getClass(node.instance), node.instance);
+			}
+		}
+
+		if (readyEvent) {
+			new Element(".nodes").ready(function(e) {
+				if (IsVisible()) {
+					generateEdges();
+				}
+			});
+		} else {
+			generateEdges();
+		}
+
+
+		for (p in shaderGraph.parametersAvailable) {
+			var pElt = addParameter(p.id, p.name, p.type, p.defaultValue);
+			if (saveToggleParams.get(p.id)) {
+				toggleParameter(pElt, true);
+			}
+		}
+
+		launchCompileShader();
+	}
+
 	function generateEdges() {
 		for (box in listOfBoxes) {
 			for (key in box.getInstance().getInputsKey()) {
@@ -349,6 +379,7 @@ class ShaderEditor extends hide.view.Graph {
 	}
 
 	function addParameter(id : Int, name : String, type : Type, ?value : Dynamic) {
+
 		var elt = new Element('<div id="param_${id}" class="parameter" draggable="true" ></div>').appendTo(parametersList);
 		var content = new Element('<div class="content" ></div>');
 		content.hide();
@@ -362,23 +393,18 @@ class ShaderEditor extends hide.view.Graph {
 				var rangeInput = @:privateAccess range.f;
 				rangeInput.on("mousedown", function(e) {
 					elt.attr("draggable", "false");
+					beforeChange();
 				});
 				rangeInput.on("mouseup", function(e) {
 					elt.attr("draggable", "true");
+					afterChange();
 				});
-				if (value != null && value.length > 0) range.value = value;
-				range.onChange = function(temp) {
+				if (value != null) range.value = value;
+				range.onChange = function(moving) {
 					if (!shaderGraph.setParameterDefaultValue(id, range.value))
 						return;
-					var param = shaderGraph.getParameter(id);
-					for (b in listOfBoxes) {
-						var shaderParam = Std.instance(b.getInstance(), ShaderParam);
-						if (shaderParam != null && shaderParam.parameterId == id) {
-							setDisplayValue(shaderParam, param.type, param.defaultValue);
-							b.generateProperties(editor);
-						}
-					}
-					updateParam(param);
+					setBoxesParam(id);
+					updateParam(id);
 				};
 				typeName = "Number";
 			case TVec(4, VFloat):
@@ -391,21 +417,19 @@ class ShaderEditor extends hide.view.Graph {
 				else
 					start = h3d.Vector.fromArray([0, 0, 0, 1]);
 				picker.value = start.toColor();
-
 				picker.onChange = function(move) {
 					var vecColor = h3d.Vector.fromColor(picker.value);
 					if (!shaderGraph.setParameterDefaultValue(id, [vecColor.x, vecColor.y, vecColor.z, vecColor.w]))
 						return;
-					var param = shaderGraph.getParameter(id);
-					for (b in listOfBoxes) {
-						var shaderParam = Std.instance(b.getInstance(), ShaderParam);
-						if (shaderParam != null && shaderParam.parameterId == id) {
-							setDisplayValue(shaderParam, param.type, param.defaultValue);
-							b.generateProperties(editor);
-						}
-					}
-					updateParam(param);
+					setBoxesParam(id);
+					updateParam(id);
 				};
+				picker.element.on("dragstart.spectrum", function() {
+					beforeChange();
+				});
+				picker.element.on("dragstop.spectrum", function() {
+					afterChange();
+				});
 				typeName = "Color";
 			case TSampler2D:
 				var parentSampler = new Element('<input type="texturepath" field="sampler2d" />').appendTo(defaultValue);
@@ -413,17 +437,12 @@ class ShaderEditor extends hide.view.Graph {
 				var tselect = new hide.comp.TextureSelect(null, parentSampler);
 				if (value != null && value.length > 0) tselect.path = value;
 				tselect.onChange = function() {
+					beforeChange();
 					if (!shaderGraph.setParameterDefaultValue(id, tselect.path))
 						return;
-					var param = shaderGraph.getParameter(id);
-					for (b in listOfBoxes) {
-						var shaderParam = Std.instance(b.getInstance(), ShaderParam);
-						if (shaderParam != null && shaderParam.parameterId == id) {
-							setDisplayValue(shaderParam, param.type, param.defaultValue);
-							b.generateProperties(editor);
-						}
-					}
-					updateParam(param);
+					afterChange();
+					setBoxesParam(id);
+					updateParam(id);
 				}
 				typeName = "Texture";
 			default:
@@ -451,7 +470,9 @@ class ShaderEditor extends hide.view.Graph {
 					return;
 				}
 			}
+			beforeChange();
 			shaderGraph.removeParameter(id);
+			afterChange();
 			elt.remove();
 		});
 		deleteBtn.appendTo(actionBtns);
@@ -466,7 +487,9 @@ class ShaderEditor extends hide.view.Graph {
 				for (b in listOfBoxes) {
 					var shaderParam = Std.instance(b.getInstance(), ShaderParam);
 					if (shaderParam != null && shaderParam.parameterId == id) {
+						beforeChange();
 						shaderParam.setName(newName);
+						afterChange();
 					}
 				}
 			}
@@ -482,6 +505,17 @@ class ShaderEditor extends hide.view.Graph {
 		return elt;
 	}
 
+	function setBoxesParam(id : Int) {
+		var param = shaderGraph.getParameter(id);
+		for (b in listOfBoxes) {
+			var shaderParam = Std.instance(b.getInstance(), ShaderParam);
+			if (shaderParam != null && shaderParam.parameterId == id) {
+				setDisplayValue(shaderParam, param.type, param.defaultValue);
+				b.generateProperties(editor);
+			}
+		}
+	}
+
 	function setDisplayValue(node : ShaderParam, type : Type, defaultValue : Dynamic) {
 		switch (type) {
 			case TSampler2D:
@@ -528,7 +562,9 @@ class ShaderEditor extends hide.view.Graph {
 	}
 
 	function createParameter(type : Type) {
+		beforeChange();
 		var paramShaderID = shaderGraph.addParameter(type);
+		afterChange();
 		var paramShader = shaderGraph.getParameter(paramShaderID);
 
 		var elt = addParameter(paramShaderID, paramShader.name, type, null);
@@ -607,7 +643,8 @@ class ShaderEditor extends hide.view.Graph {
 		}
 	}
 
-	function updateParam(param : Parameter) {
+	function updateParam(id : Int) {
+		var param = shaderGraph.getParameter(id);
 		setParamValue(currentShader, param.variable, param.defaultValue);
 	}
 
@@ -629,7 +666,10 @@ class ShaderEditor extends hide.view.Graph {
 	}
 
 	function addNode(p : Point, nodeClass : Class<ShaderNode>) {
+		beforeChange();
+
 		var node = shaderGraph.addNode(p.x, p.y, nodeClass);
+		afterChange();
 
 		addBox(p, nodeClass, node);
 
@@ -658,7 +698,9 @@ class ShaderEditor extends hide.view.Graph {
 			}
 		}
 		try {
+			beforeChange();
 			if (shaderGraph.addEdge({ idOutput: startLinkBox.getId(), nameOutput: startLinkNode.attr("field"), idInput: endLinkBox.getId(), nameInput: endLinkNode.attr("field") })) {
+				afterChange();
 				createEdgeInEditorGraph(newEdge);
 				currentLink.removeClass("draft");
 				currentLink = null;
@@ -894,6 +936,22 @@ class ShaderEditor extends hide.view.Graph {
 		customContextMenu(elements, x, y);
 	}
 
+	function beforeChange() {
+		lastSnapshot = haxe.Json.parse(shaderGraph.save());
+	}
+
+	function afterChange() {
+		var newVal = haxe.Json.parse(shaderGraph.save());
+		var oldVal = lastSnapshot;
+		undo.change(Custom(function(undo) {
+			if (undo)
+				shaderGraph.load(oldVal);
+			else
+				shaderGraph.load(newVal);
+			refreshShaderGraph(false);
+		}));
+	}
+
 	// Graph methods
 
 	override function addBox(p : Point, nodeClass : Class<ShaderNode>, node : ShaderNode) : Box {
@@ -921,12 +979,16 @@ class ShaderEditor extends hide.view.Graph {
 
 	override function removeBox(box : Box) {
 		super.removeBox(box);
+		beforeChange();
 		shaderGraph.removeNode(box.getId());
+		afterChange();
 	}
 
 	override function removeEdge(edge : Graph.Edge) {
 		super.removeEdge(edge);
+		beforeChange();
 		shaderGraph.removeEdge(edge.to.getId(), edge.nodeTo.attr("field"));
+		afterChange();
 		launchCompileShader();
 	}
 

+ 15 - 9
hrt/shgraph/ShaderGraph.hx

@@ -36,7 +36,6 @@ class ShaderGraph {
 	var current_param_id = 0;
 	var filepath : String;
 	var nodes : Map<Int, Node> = [];
-	var allVariables : Array<TVar> = [];
 	public var parametersAvailable : Map<Int, Parameter> = [];
 
 	public function new(filepath : String) {
@@ -50,19 +49,27 @@ class ShaderGraph {
 			throw "Invalid shader graph parsing ("+e+")";
 		}
 
-		generate(Reflect.getProperty(json, "nodes"), Reflect.getProperty(json, "edges"), Reflect.getProperty(json, "parameters"));
+		load(json);
+
+	}
 
+	public function load(json : haxe.Json) {
+		nodes = [];
+		parametersAvailable = [];
+		generate(Reflect.getProperty(json, "nodes"), Reflect.getProperty(json, "edges"), Reflect.getProperty(json, "parameters"));
 	}
 
 	public function generate(nodes : Array<Node>, edges : Array<Edge>, parameters : Array<Parameter>) {
 
 		for (p in parameters) {
 			var typeString : Array<Dynamic> = Reflect.field(p, "type");
-			if (typeString[1] == null || typeString[1].length == 0)
-				p.type = std.Type.createEnum(Type, typeString[0]);
-			else {
-				var paramsEnum = typeString[1].split(",");
-				p.type = std.Type.createEnum(Type, typeString[0], [Std.parseInt(paramsEnum[0]), std.Type.createEnum(VecType, paramsEnum[1])]);
+			if (Std.is(typeString, Array)) {
+				if (typeString[1] == null || typeString[1].length == 0)
+					p.type = std.Type.createEnum(Type, typeString[0]);
+				else {
+					var paramsEnum = typeString[1].split(",");
+					p.type = std.Type.createEnum(Type, typeString[0], [Std.parseInt(paramsEnum[0]), std.Type.createEnum(VecType, paramsEnum[1])]);
+				}
 			}
 			p.variable = generateParameter(p.name, p.type);
 			this.parametersAvailable.set(p.id, p);
@@ -189,7 +196,7 @@ class ShaderGraph {
 
 	public function compile() : hrt.prefab.ContextShared.ShaderDef {
 
-		allVariables = [];
+		var allVariables : Array<TVar> = [];
 		var allParameters = [];
 		var allParamDefaultValue = [];
 		var content = [];
@@ -362,7 +369,6 @@ class ShaderGraph {
 				edgesJson.push({ idOutput: output.node.id, nameOutput: output.keyOutput, idInput: n.id, nameInput: k });
 			}
 		}
-
 		var json = haxe.Json.stringify({
 			nodes: [
 				for (n in nodes) { x : n.x, y : n.y, comment: n.comment, id: n.id, type: n.type, properties : n.instance.savePropertiesNode() }

+ 1 - 1
hrt/shgraph/ShaderNode.hx

@@ -28,7 +28,7 @@ class ShaderNode {
 
 	public function setInput(key : String, s : NodeVar) {
 		if (s == null)
-			inputs.remove(key);
+				inputs.remove(key);
 		else
 			inputs.set(key, s);
 	}

+ 30 - 0
hrt/shgraph/nodes/Lerp.hx

@@ -0,0 +1,30 @@
+package hrt.shgraph.nodes;
+
+using hxsl.Ast;
+
+@name("Lerp")
+@description("Linear interpolation between Min and Max using A")
+@width(80)
+@group("Math")
+class Lerp extends ShaderFunction {
+
+	@input("min") var x = SType.Number;
+	@input("max") var y = SType.Number;
+	@input("A") var a = SType.Number;
+
+	public function new() {
+		super(Mix);
+	}
+
+	override public function computeOutputs() {
+		if (x != null && !x.isEmpty() && y != null && !y.isEmpty())
+			addOutput("output", x.getVar(y.getType()).t);
+		else if (x != null && !x.isEmpty() )
+			addOutput("output", x.getType());
+		else if (y != null && !y.isEmpty())
+			addOutput("output", y.getType());
+		else
+			removeOutput("output");
+	}
+
+}