Browse Source

ShaderGraph : Subgraph node (WIP : static input node)

Tom SPIRA 6 years ago
parent
commit
5b1194e084

+ 60 - 61
hide/view/Graph.hx

@@ -282,66 +282,60 @@ class Graph extends FileView {
 		});
 		listOfBoxes.push(box);
 
-		var fields = std.Type.getInstanceFields(nodeClass);
-
-		var metas = haxe.rtti.Meta.getFields(nodeClass);
-		var metasParent = haxe.rtti.Meta.getFields(std.Type.getSuperClass(nodeClass));
-		for (f in fields) {
-			var m = Reflect.field(metas, f);
-			if (m == null) {
-				m = Reflect.field(metasParent, f);
-				if (m == null) continue;
+		for (inputKey in box.getInstance().getInputInfoKeys()) {
+			var inputInfo = box.getInstance().getInputInfo(inputKey);
+
+			if (inputInfo == null) {
+				trace(inputKey);
 			}
-			if (Reflect.hasField(m, "input")) {
-				var inputMeta : Array<Dynamic> = Reflect.field(m, "input");
-				var name : String = (m.input != null && m.input.length > 0) ? inputMeta[0] : "input";
-
-				var defaultValue = null;
-				if (m.input.length >= 2 && inputMeta[1]) {
-					defaultValue = Reflect.field(box.getInstance(), 'prop_${f}');
-					if (defaultValue == null) {
-						defaultValue = "0";
-					}
-				}
-				var grNode = box.addInput(editor, name, defaultValue);
-				if (defaultValue != null) {
-					var fieldEditInput = grNode.find("input");
-					fieldEditInput.on("change", function(ev) {
-						var tmpValue = Std.parseFloat(fieldEditInput.val());
-						if (Math.isNaN(tmpValue) ) {
-							fieldEditInput.addClass("error");
-						} else {
-							Reflect.setField(box.getInstance(), 'prop_${f}', tmpValue);
-							fieldEditInput.val(tmpValue);
-							fieldEditInput.removeClass("error");
-						}
-					});
+
+			var defaultValue = null;
+			if (inputInfo.hasProperty) {
+				defaultValue = Reflect.field(box.getInstance(), 'prop_${inputKey}');
+				if (defaultValue == null) {
+					defaultValue = "0";
 				}
-				grNode.find(".node").attr("field", f);
-				grNode.on("mousedown", function(e : js.jquery.Event) {
-					e.stopPropagation();
-					var node = grNode.find(".node");
-					if (node.attr("hasLink") != null) {
-						replaceEdge(FromOutput, node, e.clientX, e.clientY);
-						return;
+			}
+			var grNode = box.addInput(editor, inputInfo.name, defaultValue);
+			if (defaultValue != null) {
+				var fieldEditInput = grNode.find("input");
+				fieldEditInput.on("change", function(ev) {
+					var tmpValue = Std.parseFloat(fieldEditInput.val());
+					if (Math.isNaN(tmpValue) ) {
+						fieldEditInput.addClass("error");
+					} else {
+						Reflect.setField(box.getInstance(), 'prop_${inputKey}', tmpValue);
+						fieldEditInput.val(tmpValue);
+						fieldEditInput.removeClass("error");
 					}
-					isCreatingLink = FromInput;
-					startLinkGrNode = grNode;
-					startLinkBox = box;
-					setAvailableOutputNodes(box, grNode.find(".node").attr("field"));
-				});
-			} else if (Reflect.hasField(m, "output")) {
-				var name : String = (m.output != null && m.output.length > 0) ? Reflect.field(m, "output")[0] : "output";
-				var grNode = box.addOutput(editor, name);
-				grNode.find(".node").attr("field", f);
-				grNode.on("mousedown", function(e) {
-					e.stopPropagation();
-					isCreatingLink = FromOutput;
-					startLinkGrNode = grNode;
-					startLinkBox = box;
-					setAvailableInputNodes(box, startLinkGrNode.find(".node").attr("field"));
 				});
 			}
+			grNode.find(".node").attr("field", inputKey);
+			grNode.on("mousedown", function(e : js.jquery.Event) {
+				e.stopPropagation();
+				var node = grNode.find(".node");
+				if (node.attr("hasLink") != null) {
+					replaceEdge(FromOutput, node, e.clientX, e.clientY);
+					return;
+				}
+				isCreatingLink = FromInput;
+				startLinkGrNode = grNode;
+				startLinkBox = box;
+				setAvailableOutputNodes(box, grNode.find(".node").attr("field"));
+			});
+		}
+		for (outputKey in box.getInstance().getOutputInfoKeys()) {
+			var outputInfo = box.getInstance().getOutputInfo(outputKey);
+
+			var grNode = box.addOutput(editor, outputInfo.name);
+			grNode.find(".node").attr("field", outputKey);
+			grNode.on("mousedown", function(e) {
+				e.stopPropagation();
+				isCreatingLink = FromOutput;
+				startLinkGrNode = grNode;
+				startLinkBox = box;
+				setAvailableInputNodes(box, startLinkGrNode.find(".node").attr("field"));
+			});
 		}
 
 		box.generateProperties(editor);
@@ -350,6 +344,12 @@ class Graph extends FileView {
 	}
 
 	function removeBox(box : Box) {
+		removeEdges(box);
+		box.dispose();
+		listOfBoxes.remove(box);
+	}
+
+	function removeEdges(box : Box) {
 		var length = listOfEdges.length;
 		for (i in 0...length) {
 			var edge = listOfEdges[length-i-1];
@@ -357,8 +357,6 @@ class Graph extends FileView {
 				removeEdge(edge); // remove edge from listOfEdges
 			}
 		}
-		box.dispose();
-		listOfBoxes.remove(box);
 	}
 
 	function removeEdge(edge : Edge) {
@@ -403,9 +401,9 @@ class Graph extends FileView {
 		var type = boxOutput.getInstance().getOutputType(field);
 		var sType : SType;
 		if (type == null) {
-			sType = boxOutput.getInstance().getOutputInfo(field);
+			sType = boxOutput.getInstance().getOutputInfo(field).type;
 		} else {
-			sType = ShaderType.getType(type);
+			sType = ShaderType.getSType(type);
 		}
 
 		for (box in listOfBoxes) {
@@ -424,9 +422,9 @@ class Graph extends FileView {
 				var type = box.getInstance().getOutputType(outputField);
 				var sType : SType;
 				if (type == null) {
-					sType = box.getInstance().getOutputInfo(outputField);
+					sType = box.getInstance().getOutputInfo(outputField).type;
 				} else {
-					sType = ShaderType.getType(type);
+					sType = ShaderType.getSType(type);
 				}
 				if (boxInput.getInstance().checkTypeAndCompatibilyInput(field, sType)) {
 					output.addClass("nodeMatch");
@@ -660,6 +658,7 @@ class Graph extends FileView {
 	}
 
 	function centerView() {
+		if (listOfBoxes.length == 0) return;
 		var xMin = listOfBoxes[0].getX();
 		var yMin = listOfBoxes[0].getY();
 		var xMax = xMin + listOfBoxes[0].getWidth();

+ 8 - 3
hide/view/shadereditor/Box.hx

@@ -25,6 +25,7 @@ class Box {
 	public var outputs : Array<JQuery> = [];
 
 	var hasHeader : Bool = true;
+	var hadToShowInputs : Bool = false;
 	var color : String;
 
 	var element : JQuery;
@@ -60,6 +61,10 @@ class Box {
 			editor.text(element, 7, HEADER_HEIGHT-6, className).addClass("title-box");
 		}
 
+		if (Reflect.hasField(metas, "alwaysshowinputs")) {
+			hadToShowInputs = true;
+		}
+
 		propertiesGroup = editor.group(element).addClass("properties-group");
 
 		// nodes div
@@ -108,7 +113,7 @@ class Box {
 
 		if (props.length == 0) return;
 
-		if (inputs.length <= 1 && outputs.length <= 1) {
+		if (!hadToShowInputs && inputs.length <= 1 && outputs.length <= 1) {
 			element.find(".nodes").remove();
 			element.find(".input-node-group > .title-node").html("");
 			element.find(".output-node-group > .title-node").html("");
@@ -150,7 +155,7 @@ class Box {
 		if (inputs.length >= 1 && outputs.length >= 1) {
 			element.find(".nodes-separator").attr("y2", HEADER_HEIGHT + height);
 			element.find(".nodes-separator").show();
-		} else {
+		} else if (!hadToShowInputs) {
 			element.find(".nodes-separator").hide();
 		}
 
@@ -193,7 +198,7 @@ class Box {
 	}
 	public function getNodesHeight() {
 		var maxNb = Std.int(Math.max(inputs.length, outputs.length));
-		if (maxNb == 1 && propsHeight > 0) {
+		if (!hadToShowInputs && maxNb == 1 && propsHeight > 0) {
 			return 0;
 		}
 		return NODE_MARGIN * (maxNb+1) + NODE_RADIUS * maxNb;

+ 133 - 23
hide/view/shadereditor/ShaderEditor.hx

@@ -227,7 +227,26 @@ class ShaderEditor extends hide.view.Graph {
 
 		editorMatrix.on("change", "input, select", function(ev) {
 			try {
-				shaderGraph.nodeUpdated(ev.target.closest(".box").id);
+				var idBox = ev.target.closest(".box").id;
+				for (b in listOfBoxes) {
+					if (b.getId() == idBox) {
+						var subGraph = Std.instance(b.getInstance(), hrt.shgraph.nodes.SubGraph);
+						if (subGraph != null) {
+							var length = listOfEdges.length;
+							for (i in 0...length) {
+								var edge = listOfEdges[length-i-1];
+								if (edge.from == b || edge.to == b) {
+									removeShaderGraphEdge(edge);
+								}
+							}
+							refreshBox(b);
+							afterChange();
+							return;
+						}
+						break;
+					}
+				}
+				shaderGraph.nodeUpdated(idBox);
 				afterChange();
 				launchCompileShader();
 			} catch (e : Dynamic) {
@@ -365,26 +384,78 @@ class ShaderEditor extends hide.view.Graph {
 		launchCompileShader();
 	}
 
-	function generateEdges() {
-		for (box in listOfBoxes) {
-			for (key in box.getInstance().getInputsKey()) {
-				var input = box.getInstance().getInput(key);
-				if (input != null) {
-					var fromBox : Box = null;
-					for (boxFrom in listOfBoxes) {
-						if (boxFrom.getId() == input.node.id) {
-							fromBox = boxFrom;
-							break;
+	function generateEdgesFromBox(box : Box) {
+		for (outputKey in box.getInstance().getOutputInfoKeys()) {
+			var output = box.getInstance().getOutput(outputKey);
+			if (output != null) {
+				for (b in listOfBoxes) {
+					for (key in b.getInstance().getInputsKey()) {
+						var input = b.getInstance().getInput(key);
+						if (input != null && input.node.id == box.getId()) {
+							var nodeFrom = box.getElement().find('[field=${outputKey}]');
+							var nodeTo = b.getElement().find('[field=${key}]');
+							createEdgeInEditorGraph({from: box, nodeFrom: nodeFrom, to : b, nodeTo: nodeTo, elt : createCurve(nodeFrom, nodeTo) });
 						}
 					}
-					var nodeFrom = fromBox.getElement().find('[field=${input.getKey()}]');
-					var nodeTo = box.getElement().find('[field=${key}]');
-					createEdgeInEditorGraph({from: fromBox, nodeFrom: nodeFrom, to : box, nodeTo: nodeTo, elt : createCurve(nodeFrom, nodeTo) });
 				}
 			}
 		}
 	}
 
+	function generateEdgesToBox(box : Box) {
+		for (key in box.getInstance().getInputsKey()) {
+			var input = box.getInstance().getInput(key);
+			if (input != null) {
+				var fromBox : Box = null;
+				for (boxFrom in listOfBoxes) {
+					if (boxFrom.getId() == input.node.id) {
+						fromBox = boxFrom;
+						break;
+					}
+				}
+				var nodeFrom = fromBox.getElement().find('[field=${input.getKey()}]');
+				var nodeTo = box.getElement().find('[field=${key}]');
+				createEdgeInEditorGraph({from: fromBox, nodeFrom: nodeFrom, to : box, nodeTo: nodeTo, elt : createCurve(nodeFrom, nodeTo) });
+			}
+		}
+	}
+
+	function generateEdges() {
+		for (box in listOfBoxes) {
+			generateEdgesToBox(box);
+		}
+	}
+
+	function refreshBox(box : Box) {
+		var length = listOfEdges.length;
+		for (i in 0...length) {
+			var edge = listOfEdges[length-i-1];
+			if (edge.from == box || edge.to == box) {
+				super.removeEdge(edge);
+			}
+		}
+		var indexInputStartLink = -1;
+		if (startLinkBox == box) {
+			var nodeInputJQuery = startLinkGrNode.find(".node");
+			for (i in 0...box.inputs.length) {
+				if (box.inputs[i].is(nodeInputJQuery)) {
+					indexInputStartLink = i;
+					break;
+				}
+			}
+		}
+		var newBox : Box = addBox(new Point(box.getX(), box.getY()), std.Type.getClass(box.getInstance()), box.getInstance());
+		box.dispose();
+		listOfBoxes.remove(box);
+		generateEdgesToBox(newBox);
+		generateEdgesFromBox(newBox);
+		if (indexInputStartLink >= 0) {
+			startLinkBox = newBox;
+			startLinkGrNode = newBox.inputs[indexInputStartLink].parent();
+		}
+		return newBox;
+	}
+
 	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);
@@ -529,17 +600,20 @@ class ShaderEditor extends hide.view.Graph {
 	function setDisplayValue(node : ShaderParam, type : Type, defaultValue : Dynamic) {
 		switch (type) {
 			case TSampler2D:
-				node.setDisplayValue('file://${ide.getPath(defaultValue)}');
+				if (defaultValue != null && defaultValue.length > 0)
+					node.setDisplayValue('file://${ide.getPath(defaultValue)}');
 			case TVec(4, VFloat):
-				var vec = Vector.fromArray(defaultValue);
-				var hexa = StringTools.hex(vec.toColor(),8);
-				var hexaFormatted = "";
-				if (hexa.length == 8) {
-					hexaFormatted = hexa.substr(2, 6) + hexa.substr(0, 2);
-				} else {
-					hexaFormatted = hexa;
+				if (defaultValue != null && defaultValue.length > 0) {
+					var vec = Vector.fromArray(defaultValue);
+					var hexa = StringTools.hex(vec.toColor(),8);
+					var hexaFormatted = "";
+					if (hexa.length == 8) {
+						hexaFormatted = hexa.substr(2, 6) + hexa.substr(0, 2);
+					} else {
+						hexaFormatted = hexa;
+					}
+					node.setDisplayValue('#${hexaFormatted}');
 				}
-				node.setDisplayValue('#${hexaFormatted}');
 			default:
 				node.setDisplayValue(defaultValue);
 		}
@@ -631,14 +705,19 @@ class ShaderEditor extends hide.view.Graph {
 						}
 					} else {
 						var nameOutput = str.split("(")[1].split(" =")[0];
+						var errorSent = false;
 						for (b in listOfBoxes) {
 							var shaderOutput = Std.instance(b.getInstance(), hrt.shgraph.ShaderOutput);
 							if (shaderOutput != null) {
 								if (shaderOutput.variable.name == nameOutput) {
 									error("Compilation of shader failed > Invalid inputs", shaderOutput.id);
+									errorSent = true;
 									break;
 								}
 							}
+							if (!errorSent) {
+								error("Compilation of shader failed > " + str);
+							}
 						}
 					}
 					if (newShader != null)
@@ -649,6 +728,7 @@ class ShaderEditor extends hide.view.Graph {
 							m.mainPass.addShader(currentShader);
 						}
 					}
+					throw e;
 					return;
 				}
 			} else if (Std.is(e, ShaderException)) {
@@ -721,6 +801,13 @@ class ShaderEditor extends hide.view.Graph {
 			return node;
 		}
 
+		var subGraphNode = Std.instance(node, hrt.shgraph.nodes.SubGraph);
+		if (subGraphNode != null) {
+			subGraphNode.loadGraphShader();
+			addBox(p, nodeClass, subGraphNode);
+			return node;
+		}
+
 		addBox(p, nodeClass, node);
 
 		return node;
@@ -1006,6 +1093,27 @@ class ShaderEditor extends hide.view.Graph {
 		shaderGraph.removeEdge(edge.to.getId(), edge.nodeTo.attr("field"));
 	}
 
+	function removeEdgeSubGraphUpdate(edge : Graph.Edge) {
+		var subGraph = Std.instance(edge.to.getInstance(), hrt.shgraph.nodes.SubGraph);
+		if (subGraph != null) {
+			var field = "";
+			if (isCreatingLink == FromInput) {
+				field = edge.nodeTo.attr("field");
+			} else {
+				field = edge.nodeFrom.attr("field");
+			}
+			var newBox = refreshBox(edge.to);
+			subGraph.loadGraphShader();
+
+			clearAvailableNodes();
+			if (isCreatingLink == FromInput) {
+				setAvailableOutputNodes(newBox, field);
+			} else {
+				setAvailableInputNodes(edge.from, field);
+			}
+		}
+	}
+
 	// Graph methods
 
 	override function addBox(p : Point, nodeClass : Class<ShaderNode>, node : ShaderNode) : Box {
@@ -1039,6 +1147,7 @@ class ShaderEditor extends hide.view.Graph {
 			if (edge.from == box || edge.to == box) {
 				super.removeEdge(edge);
 				removeShaderGraphEdge(edge);
+				removeEdgeSubGraphUpdate(edge);
 			}
 		}
 		shaderGraph.removeNode(box.getId());
@@ -1053,6 +1162,7 @@ class ShaderEditor extends hide.view.Graph {
 		beforeChange();
 		removeShaderGraphEdge(edge);
 		afterChange();
+		removeEdgeSubGraphUpdate(edge);
 		launchCompileShader();
 	}
 

+ 27 - 6
hrt/shgraph/ParseFieldsMacro.hx

@@ -15,6 +15,7 @@ class ParseFieldsMacro {
 		var inputsList = new Array<String>();
 		var hasInputs = false;
 		var mapOutputs = new Array<Expr>();
+		var outputsList = new Array<String>();
 		var hasOutputs = false;
 
 		for ( f in fields ) {
@@ -29,6 +30,15 @@ class ParseFieldsMacro {
 							var get_sel = "get_" + sel;
 							var propSel = "prop_" + sel;
 							var hasProperty = false;
+							var nameInput = "input";
+							if (m.params.length >= 1) {
+								switch(m.params[0].expr) {
+									case EConst(CString(s)):
+										if (s.length > 0)
+											nameInput = s;
+									default:
+								}
+							}
 							if (m.params.length >= 2) {
 								switch(m.params[1].expr) {
 									case EConst(CIdent(b)):
@@ -68,14 +78,13 @@ class ParseFieldsMacro {
 								Context.error('Input ${sel} has not affectation', f.pos);
 
 							var enumValue = ["ShaderType", "SType", e.toString().split(".").pop()];
-							mapInputs.push(macro $v{sel} => { type : ${enumValue.toFieldExpr()}, hasProperty: $v{hasProperty} });
+							mapInputs.push(macro $v{sel} => { name : $v{nameInput}, type : ${enumValue.toFieldExpr()}, hasProperty: $v{hasProperty} });
 							f.kind = FProp("get", "null", TPath({ pack: ["hrt", "shgraph"], name: "NodeVar" }));
 							f.meta = saveMeta;
 							inputsList.push(f.name);
 
 							break;
-						}
-						if (m.name == "output") {
+						} else if (m.name == "output") {
 							hasOutputs = true;
 							var sel = f.name;
 							var get_sel = "get_" + sel;
@@ -86,10 +95,21 @@ class ParseFieldsMacro {
 								fields.push(field);
 							if (e == null)
 								Context.error('Output ${sel} has not affectation', f.pos);
+							var nameOutput = "";
+							if (m.params.length > 0) {
+								switch(m.params[0].expr) {
+									case EConst(CString(s)):
+										if (s.length > 0)
+											nameOutput = s;
+									default:
+								}
+							}
 							var enumValue = ["ShaderType", "SType", e.toString().split(".").pop()];
-							mapOutputs.push(macro $v{sel} => ${enumValue.toFieldExpr()});
+							mapOutputs.push(macro $v{sel} => { name : $v{nameOutput}, type : ${enumValue.toFieldExpr()} });
 							f.kind = FProp("get", "null", TPath({ pack: [], name: "TVar" }));
 							f.meta = saveMeta;
+							outputsList.push(f.name);
+
 							break;
 						}
 					}
@@ -114,11 +134,12 @@ class ParseFieldsMacro {
 			fields.push({
 				name: "outputsInfo",
 				access: [Access.APrivate],
-				kind: FieldType.FVar(macro:Map<String, ShaderType.SType>, macro $a{mapOutputs}),
+				kind: FieldType.FVar(macro:Map<String, ShaderNode.OutputInfo>, macro $a{mapOutputs}),
 				pos: Context.currentPos(),
 			});
 			var sfields = macro class {
-				override public function getOutputInfo(key : String) : ShaderType.SType return outputsInfo.get(key);
+				override public function getOutputInfo(key : String) : ShaderNode.OutputInfo return outputsInfo.get(key);
+				override public function getOutputInfoKeys() : Array<String> return $v{outputsList};
 			};
 			for( field in sfields.fields )
 				fields.push(field);

+ 2 - 1
hrt/shgraph/ShaderFunction.hx

@@ -18,7 +18,8 @@ class ShaderFunction extends ShaderNode {
 
 		for (k in getInputInfoKeys()) {
 			args.push({ name: k, type: getInput(k).getType() });
-			varArgs.push(getInput(k).getVar());
+			var wantedType = ShaderType.getType(getInputInfo(k).type);
+			varArgs.push(getInput(k).getVar((wantedType != null) ? wantedType : null));
 		}
 
 		return {

+ 69 - 6
hrt/shgraph/ShaderGraph.hx

@@ -41,13 +41,18 @@ class ShaderGraph {
 	var nodes : Map<Int, Node> = [];
 	public var parametersAvailable : Map<Int, Parameter> = [];
 
+	// subgraph variable
+	var variableNamesAlreadyUpdated = false;
+
 	public function new(filepath : String) {
 		if (filepath == null) return;
 		this.filepath = filepath;
 
 		var json;
 		try {
-			json = haxe.Json.parse(sys.io.File.getContent(this.filepath));
+			var content = sys.io.File.getContent(this.filepath);
+			if (content.length == 0) return;
+			json = haxe.Json.parse(content);
 		} catch( e : Dynamic ) {
 			throw "Invalid shader graph parsing ("+e+")";
 		}
@@ -82,8 +87,8 @@ class ShaderGraph {
 		for (n in nodes) {
 			n.outputs = [];
 			n.instance = std.Type.createInstance(std.Type.resolveClass(n.type), []);
-			n.instance.loadProperties(n.properties);
 			n.instance.setId(n.id);
+			n.instance.loadProperties(n.properties);
 			this.nodes.set(n.id, n);
 
 			var shaderParam = Std.instance(n.instance, ShaderParam);
@@ -170,6 +175,7 @@ class ShaderGraph {
 
 	function buildNodeVar(nodeVar : NodeVar) : Array<TExpr>{
 		var node = nodeVar.node;
+		var isSubGraph = Std.is(node, hrt.shgraph.nodes.SubGraph);
 		if (node == null)
 			return [];
 		var res = [];
@@ -179,12 +185,12 @@ class ShaderGraph {
 			if (input != null) {
 				res = res.concat(buildNodeVar(input));
 			} else if (node.getInputInfo(key).hasProperty) {
+			} else if (isSubGraph) {
 			} else {
 				throw ShaderException.t("This box has inputs not connected", node.id);
 			}
 		}
 		var build = nodeVar.getExpr();
-		res = res.concat(build);
 
 		var shaderInput = Std.instance(node, ShaderInput);
 		if (shaderInput != null) {
@@ -195,11 +201,53 @@ class ShaderGraph {
 		}
 		var shaderParam = Std.instance(node, ShaderParam);
 		if (shaderParam != null && !alreadyAddVariable(shaderParam.variable)) {
-			shaderParam.variable = generateParameter(shaderParam.variable.name, shaderParam.variable.type);
+			if (shaderParam.variable == null) {
+				shaderParam.variable = generateParameter(shaderParam.variable.name, shaderParam.variable.type);
+			}
 			allVariables.push(shaderParam.variable);
 			allParameters.push(shaderParam.variable);
 			allParamDefaultValue.push(getParameter(shaderParam.parameterId).defaultValue);
 		}
+		if (isSubGraph) {
+			var subGraph = Std.instance(node, hrt.shgraph.nodes.SubGraph);
+			var params = subGraph.subShaderGraph.parametersAvailable;
+			for (subVar in subGraph.varsSubGraph) {
+				if (subVar.kind == Param) {
+					if (!alreadyAddVariable(subVar)) {
+						allVariables.push(subVar);
+						allParameters.push(subVar);
+						var defaultValueFound = false;
+						for (param in params) {
+							if (param.variable.name == subVar.name) {
+								allParamDefaultValue.push(param.defaultValue);
+								defaultValueFound = true;
+								break;
+							}
+						}
+						if (!defaultValueFound) {
+							throw ShaderException.t("Default value of '" + subVar.name + "' parameter not found", node.id);
+						}
+					}
+				} else {
+					if (!alreadyAddVariable(subVar)) {
+						allVariables.push(subVar);
+					}
+				}
+			}
+			var buildWithoutTBlock = [];
+			for (i in 0...build.length) {
+				switch (build[i].e) {
+					case TBlock(block):
+						for (b in block) {
+							buildWithoutTBlock.push(b);
+						}
+					default:
+						buildWithoutTBlock.push(build[i]);
+				}
+			}
+			build = buildWithoutTBlock;
+		}
+		res = res.concat(build);
 		return res;
 	}
 
@@ -214,8 +262,7 @@ class ShaderGraph {
 
 	var variableNameAvailableOnlyInVertex = [];
 
-	public function compile(?specificOutput : ShaderNode) : hrt.prefab.ContextShared.ShaderDef {
-
+	public function generateShader(specificOutput : ShaderNode = null, subShaderId : Int = null) : ShaderData {
 		allVariables = [];
 		allParameters = [];
 		allParamDefaultValue = [];
@@ -223,6 +270,14 @@ class ShaderGraph {
 		var contentFragment = [];
 
 		for (n in nodes) {
+			if (subShaderId != null && Std.is(n.instance, hrt.shgraph.nodes.SubGraph)) {
+				throw ShaderException.t("A subgraph can't have a subgraph", -1);
+			}
+			if (!variableNamesAlreadyUpdated && subShaderId != null && !Std.is(n.instance, ShaderInput)) {
+				for (outputKey in n.instance.getOutputInfoKeys()) {
+					n.instance.getOutput(outputKey).name = "sub_" + subShaderId + "_" + n.instance.getOutput(outputKey).name;
+				}
+			}
 			n.instance.outputCompiled = [];
 			#if !editor
 			if (!n.instance.hasInputs()) {
@@ -230,6 +285,7 @@ class ShaderGraph {
 			}
 			#end
 		}
+		variableNamesAlreadyUpdated = true;
 
 		var outputs : Array<String> = [];
 
@@ -311,6 +367,13 @@ class ShaderGraph {
 				});
 		}
 
+		return shaderData;
+	}
+
+	public function compile(?specificOutput : ShaderNode, ?subShaderId : Int) : hrt.prefab.ContextShared.ShaderDef {
+
+		var shaderData = generateShader(specificOutput, subShaderId);
+
 		var s = new SharedShader("");
 		s.data = shaderData;
 		@:privateAccess s.initialize();

+ 7 - 2
hrt/shgraph/ShaderNode.hx

@@ -2,7 +2,8 @@ package hrt.shgraph;
 
 using hxsl.Ast;
 
-typedef InputInfo = { type : ShaderType.SType, hasProperty : Bool };
+typedef InputInfo = { name : String, type : ShaderType.SType, hasProperty : Bool, ?id : Int };
+typedef OutputInfo = { name : String, type : ShaderType.SType, ?id : Int };
 
 @:autoBuild(hrt.shgraph.ParseFieldsMacro.build())
 @:keepSub
@@ -114,7 +115,11 @@ class ShaderNode {
 		return null;
 	}
 
-	public function getOutputInfo(key : String) : ShaderType.SType {
+	public function getOutputInfoKeys() : Array<String> {
+		return [];
+	}
+
+	public function getOutputInfo(key : String) : OutputInfo {
 		return null;
 	}
 

+ 1 - 1
hrt/shgraph/ShaderOutput.hx

@@ -16,7 +16,7 @@ class ShaderOutput extends ShaderNode {
 	var components = [X, Y, Z, W];
 
 	override public function checkValidityInput(key : String, type : ShaderType.SType) : Bool {
-		return ShaderType.checkConversion(type, ShaderType.getType(variable.type));
+		return ShaderType.checkConversion(type, ShaderType.getSType(variable.type));
 	}
 
 	override public function build(key : String) : TExpr {

+ 26 - 1
hrt/shgraph/ShaderType.hx

@@ -29,7 +29,32 @@ enum SType {
 
 class ShaderType {
 
-	static public function getType(type : hxsl.Type) : SType {
+	static public function getType(type : SType) : hxsl.Type {
+		switch (type) {
+			case Vec2:
+				return TVec(2, VFloat);
+			case Vec3:
+				return TVec(3, VFloat);
+			case Vec4:
+				return TVec(4, VFloat);
+			case VecBool2:
+				return TVec(2, VBool);
+			case VecBool3:
+				return TVec(3, VBool);
+			case VecBool4:
+				return TVec(4, VBool);
+			case Bool:
+				return TBool;
+			case Float:
+				return TFloat;
+			case Sampler:
+				return TSampler2D;
+			default:
+		}
+		return null;
+	}
+
+	static public function getSType(type : hxsl.Type) : SType {
 		switch (type) {
 			case TVec(2, VFloat):
 				return Vec2;

+ 1 - 1
hrt/shgraph/nodes/BoolConst.hx

@@ -28,7 +28,7 @@ class BoolConst extends ShaderConst {
 	override public function getPropertiesHTML(width : Float) : Array<hide.Element> {
 		var elements = super.getPropertiesHTML(width);
 		var element = new hide.Element('<div style="width: 15px; height: 30px"></div>');
-		element.append(new hide.Element('<input type="checkbox" id="value" ></select>'));
+		element.append(new hide.Element('<input type="checkbox" id="value" />'));
 
 		var input = element.children("input");
 		input.on("change", function(e) {

+ 2 - 2
hrt/shgraph/nodes/Cond.hx

@@ -17,10 +17,10 @@ class Cond extends ShaderNode {
 	override public function checkValidityInput(key : String, type : ShaderType.SType) : Bool {
 
 		if (key == "leftVar" && rightVar != null && !rightVar.isEmpty())
-			return ShaderType.checkCompatibilities(type, ShaderType.getType(rightVar.getType()));
+			return ShaderType.checkCompatibilities(type, ShaderType.getSType(rightVar.getType()));
 
 		if (key == "rightVar" && leftVar != null && !leftVar.isEmpty())
-			return ShaderType.checkCompatibilities(type, ShaderType.getType(leftVar.getType()));
+			return ShaderType.checkCompatibilities(type, ShaderType.getSType(leftVar.getType()));
 
 		return true;
 	}

+ 1 - 1
hrt/shgraph/nodes/FloatConst.hx

@@ -33,7 +33,7 @@ class FloatConst extends ShaderConst {
 	override public function getPropertiesHTML(width : Float) : Array<hide.Element> {
 		var elements = super.getPropertiesHTML(width);
 		var element = new hide.Element('<div style="width: 75px; height: 30px"></div>');
-		element.append(new hide.Element('<input type="text" id="value" style="width: ${width*0.65}px" value="${value}" />'));
+		element.append(new hide.Element('<input type="text" id="value" style="width: ${width*0.5}px" value="${value}" />'));
 
 		var input = element.children("input");
 		input.on("change", function(e) {

+ 2 - 2
hrt/shgraph/nodes/IfCondition.hx

@@ -16,10 +16,10 @@ class IfCondition extends ShaderNode {
 	override public function checkValidityInput(key : String, type : ShaderType.SType) : Bool {
 
 		if (key == "trueVar" && falseVar != null && !falseVar.isEmpty())
-			return ShaderType.checkCompatibilities(type, ShaderType.getType(falseVar.getType()));
+			return ShaderType.checkCompatibilities(type, ShaderType.getSType(falseVar.getType()));
 
 		if (key == "falseVar" && trueVar != null && !trueVar.isEmpty())
-			return ShaderType.checkCompatibilities(type, ShaderType.getType(trueVar.getType()));
+			return ShaderType.checkCompatibilities(type, ShaderType.getSType(trueVar.getType()));
 
 		return true;
 	}

+ 1 - 1
hrt/shgraph/nodes/Sin.hx

@@ -8,7 +8,7 @@ using hxsl.Ast;
 @group("Math")
 class Sin extends ShaderFunction {
 
-	@input("A") var a = SType.Number;
+	@input("A") var a = SType.Float;
 
 	public function new() {
 		super(Sin);

+ 212 - 0
hrt/shgraph/nodes/SubGraph.hx

@@ -0,0 +1,212 @@
+package hrt.shgraph.nodes;
+
+using hxsl.Ast;
+
+@name("SubGraph")
+@description("Include a subgraph")
+@group("Other")
+@width(250)
+@alwaysshowinputs()
+class SubGraph extends ShaderNode {
+
+	@prop() var pathShaderGraph : String;
+
+	var inputsInfo : Map<String, ShaderNode.InputInfo>;
+	var inputInfoKeys : Array<String> = [];
+	var outputsInfo : Map<String, ShaderNode.OutputInfo>;
+	var outputInfoKeys : Array<String> = [];
+	var parameters : Array<ShaderGraph.Parameter> = [];
+	var propertiesSubGraph : Map<Int, Dynamic>;
+
+	public var subShaderGraph : ShaderGraph;
+
+	public var varsSubGraph : Array<TVar> = [];
+
+	public function loadGraphShader() {
+		if (this.pathShaderGraph != null) {
+			try {
+				subShaderGraph = new ShaderGraph("E:/Projects/arena/trunk/res/" + pathShaderGraph);
+			} catch (e : Dynamic) {
+				trace("The shader doesn't not exist.");
+				return;
+			}
+			inputsInfo = new Map<String, ShaderNode.InputInfo>();
+			inputInfoKeys = [];
+			outputsInfo = new Map<String, ShaderNode.OutputInfo>();
+			outputInfoKeys = [];
+			parameters = [];
+			propertiesSubGraph = new Map<Int, Dynamic>();
+			var prefixSubGraph = "shgraph_" + id + "_";
+
+			for (node in subShaderGraph.getNodes()) {
+				switch (node.type.split(".").pop()) {
+					case "ShaderParam": // params become inputs
+						var shaderParam = Std.instance(node.instance, ShaderParam);
+						var paramName = subShaderGraph.getParameter(shaderParam.parameterId).name;
+
+						inputsInfo.set(prefixSubGraph+node.id, { name : paramName , type: ShaderType.getSType(shaderParam.variable.type), hasProperty: false, id : node.id });
+						inputInfoKeys.push(prefixSubGraph+node.id);
+					case "ShaderInput":
+						var shaderInput = Std.instance(node.instance, ShaderInput);
+
+						inputsInfo.set(prefixSubGraph+node.id, { name : "*" + shaderInput.variable.name , type: ShaderType.getSType(shaderInput.variable.type), hasProperty: false, id : node.id });
+						inputInfoKeys.push(prefixSubGraph+node.id);
+					case "ShaderOutput":
+						var shaderOutput = Std.instance(node.instance, ShaderOutput);
+
+						outputsInfo.set(prefixSubGraph+node.id, { name : shaderOutput.variable.name , type: ShaderType.getSType(shaderOutput.variable.type), id : node.id });
+						outputInfoKeys.push(prefixSubGraph+node.id);
+
+						addOutput(prefixSubGraph+node.id, shaderOutput.variable.type);
+					default:
+						var shaderConst = Std.instance(node.instance, ShaderConst);
+						if (shaderConst != null) { // input static become properties
+							if (Std.is(shaderConst, BoolConst)) {
+								parameters.push({ name : "Bool", type : TBool, defaultValue : null, id : shaderConst.id });
+							} else if (Std.is(shaderConst, FloatConst)) {
+								parameters.push({ name : "Number", type : TFloat, defaultValue : null, id : shaderConst.id });
+							} else if (Std.is(shaderConst, Color)) {
+								parameters.push({ name : "Color", type : TVec(4, VFloat), defaultValue : null, id : shaderConst.id });
+							}
+						}
+				}
+			}
+
+		}
+	}
+
+	override public function build(key : String) : TExpr {
+
+		for (inputKey in inputInfoKeys) {
+			var inputInfo = inputsInfo.get(inputKey);
+			var inputTVar = getInput(inputKey);
+
+			if (inputTVar != null) {
+				var nodeToReplace = subShaderGraph.getNodes().get(inputInfo.id);
+				for (i in 0...nodeToReplace.outputs.length) {
+					var inputNode = nodeToReplace.outputs[i];
+
+					for (inputKey in inputNode.instance.getInputsKey()) {
+						var input = inputNode.instance.getInput(inputKey);
+						if (input.node == nodeToReplace.instance) {
+							inputNode.instance.setInput(inputKey, inputTVar);
+						}
+					}
+				}
+			}
+		}
+
+		var shaderDef;
+		try {
+			shaderDef = subShaderGraph.generateShader(null, id);
+		} catch (e : Dynamic) {
+			throw ShaderException.t(e.msg, id);
+		}
+		if (shaderDef.funs.length > 1) {
+			throw ShaderException.t("The sub shader is vertex and fragment.", id);
+		}
+		varsSubGraph = shaderDef.vars;
+		var arrayExpr : Array<TExpr> = [];
+		switch (shaderDef.funs[0].expr.e) {
+			case TBlock(block):
+				arrayExpr = block;
+			default:
+
+		}
+
+		for (outputKey in outputInfoKeys) {
+			var outputInfo = outputsInfo.get(outputKey);
+			var outputTVar = getOutput(outputKey);
+			if (outputTVar != null) {
+				arrayExpr.push({
+					p : null,
+					t : outputTVar.type,
+					e : TBinop(OpAssign, {
+							e: TVar(outputTVar),
+							p: null,
+							t: outputTVar.type
+						}, subShaderGraph.getNodes().get(outputInfo.id).instance.getInput("input").getVar(outputTVar.type))
+				});
+			}
+		}
+
+		return {
+				p : null,
+				t : TVoid,
+				e : TBlock(arrayExpr)
+			};
+	}
+
+	override public function getInputInfo(key : String) : ShaderNode.InputInfo {
+		return inputsInfo.get(key);
+	}
+
+	override public function getInputInfoKeys() : Array<String> {
+		return inputInfoKeys;
+	}
+
+	override public function getOutputInfo(key : String) : ShaderNode.OutputInfo {
+		return outputsInfo.get(key);
+	}
+
+	override public function getOutputInfoKeys() : Array<String> {
+		return outputInfoKeys;
+	}
+
+	override public function loadProperties(props : Dynamic) {
+		this.pathShaderGraph = Reflect.field(props, "pathShaderGraph");
+		loadGraphShader();
+	}
+
+	override public function saveProperties() : Dynamic {
+		var properties = {
+			pathShaderGraph: this.pathShaderGraph
+		};
+
+		return properties;
+	}
+
+	#if editor
+	override public function getPropertiesHTML(width : Float) : Array<hide.Element> {
+		var elements = super.getPropertiesHTML(width);
+		var element = new hide.Element('<div style="width: ${width * 0.8}px; height: 25px"></div>');
+		var fileInput = new hide.Element('<input type="text" field="filesubgraph" />').appendTo(element);
+
+		fileInput.on("mousedown", function(e) {
+			e.stopPropagation();
+		});
+
+		var tfile = new hide.comp.FileSelect(["hlshader"], null, fileInput);
+		if (this.pathShaderGraph != null && this.pathShaderGraph.length > 0) tfile.path = this.pathShaderGraph;
+		tfile.onChange = function() {
+			this.pathShaderGraph = tfile.path;
+			loadGraphShader();
+			fileInput.trigger("change");
+		}
+		elements.push(element);
+		elements.push(new hide.Element('<div style="background: #202020; height: 1px; margin-bottom: 5px;"></div>'));
+
+		for (p in parameters) {
+			var element = new hide.Element('<div style="width: 100px; height: 25px"></div>');
+			element.on("mousedown", function(e) {
+				e.stopPropagation();
+			});
+			switch (p.type) {
+				case TBool:
+					element.append(new hide.Element('<input type="checkbox" id="value" />'));
+				case TFloat:
+					element.append(new hide.Element('<input type="text" id="value" style="width: ${width*0.65}px" value="" />'));
+				case TVec(4, VFloat):
+					new hide.comp.ColorPicker(true, element);
+				default:
+
+			}
+			elements.push(element);
+		}
+
+
+		return elements;
+	}
+	#end
+
+}