فهرست منبع

Shader Editor

Tom SPIRA 6 سال پیش
والد
کامیت
8c46f1bb81

+ 163 - 0
bin/style.css

@@ -1028,6 +1028,169 @@ input[type=checkbox]:checked:after {
 .terrain-surfaces .surface:hover .tooltiptext {
   visibility: visible;
 }
+/* Shader Editor */
+.shader-view #add-menu {
+  position: absolute;
+  width: 400px;
+  box-shadow: 0px 0px 25px -4px black;
+  background-color: #222222;
+  border: 1px solid #444;
+}
+.shader-view #add-menu .search-container {
+  display: flex;
+}
+.shader-view #add-menu .search-container .icon {
+  width: 16px;
+  font-size: 15px;
+  text-align: center;
+  padding: 7px;
+  border: solid #444;
+  background: #2d2d2d;
+  border-width: 0 1px 1px 0;
+}
+.shader-view #add-menu .search-container .search-bar {
+  flex: 1;
+  margin: -1px 11px 0px -1px;
+}
+.shader-view #add-menu .search-container .search-bar input {
+  width: 100%;
+  height: 30px;
+  padding-left: 10px;
+  outline: none;
+}
+.shader-view #add-menu .search-container .search-bar input:focus {
+  border: 1px solid #888888;
+}
+.shader-view #add-menu #results {
+  max-height: 300px;
+  overflow-y: auto;
+}
+.shader-view #add-menu #results div {
+  padding: 5px;
+  border-top: 1px solid #444444;
+  cursor: pointer;
+  color: #d4d4d4;
+  position: relative;
+}
+.shader-view #add-menu #results div.group {
+  background: #474747;
+  color: #c0c0c0;
+  cursor: default;
+  z-index: 1;
+  box-shadow: 0px 0px 34px -9px black;
+}
+.shader-view #add-menu #results div.group:hover {
+  background: #474747!important;
+  color: #dadada!important;
+  box-shadow: none;
+}
+.shader-view #add-menu #results div > span:first-child {
+  font-weight: bold;
+  padding-left: 10px;
+}
+.shader-view #add-menu #results div > span:last-child {
+  font-style: oblique;
+  padding-left: 7px;
+}
+.shader-view #add-menu #results div:first-child {
+  border-top: 0;
+}
+.shader-view #add-menu #results div:hover,
+.shader-view #add-menu #results div.selected {
+  background: #5eb7bf;
+  color: #000000;
+  box-shadow: inset 0px 0px 12px -1px black;
+  z-index: 3;
+}
+.shader-view .heaps-scene #preview {
+  position: absolute;
+  width: 300px;
+  height: 300px;
+  background-color: #111;
+  border: 1px solid #444;
+  right: 290px;
+  bottom: 10px;
+}
+.shader-view .heaps-scene svg g {
+  stroke: #202020;
+}
+.shader-view .heaps-scene svg g.selected .outline {
+  stroke: #668fff;
+}
+.shader-view .heaps-scene svg .outline {
+  fill: none;
+}
+.shader-view .heaps-scene svg .head-box {
+  fill: #424242;
+}
+.shader-view .heaps-scene svg .title-box {
+  fill: #ffffff;
+  stroke: none;
+  user-select: none;
+}
+.shader-view .heaps-scene svg .title-box::selection {
+  background: none;
+}
+.shader-view .heaps-scene svg .nodes,
+.shader-view .heaps-scene svg .parameters {
+  fill: #5c5c5c;
+  opacity: 0.75;
+}
+.shader-view .heaps-scene svg .input-node,
+.shader-view .heaps-scene svg .output-node {
+  fill: #c8c8c8;
+  stroke-width: 15px;
+  stroke-opacity: 0;
+}
+.shader-view .heaps-scene svg .input-node.nodeMatch,
+.shader-view .heaps-scene svg .output-node.nodeMatch {
+  fill: #5ca4ab;
+}
+.shader-view .heaps-scene svg .title-node,
+.shader-view .heaps-scene svg .title-param {
+  fill: #ffffff;
+  stroke: none;
+  user-select: none;
+}
+.shader-view .heaps-scene svg .title-node::selection,
+.shader-view .heaps-scene svg .title-param::selection {
+  background: none;
+}
+.shader-view .heaps-scene svg .parameters-group span {
+  color: #ffffff;
+  user-select: none;
+}
+.shader-view .heaps-scene svg .parameters-group span::selection {
+  background: none;
+}
+.shader-view .heaps-scene svg .parameters-group input[type=text],
+.shader-view .heaps-scene svg .parameters-group select {
+  width: 100%;
+}
+.shader-view .heaps-scene svg .edge {
+  stroke-width: 2;
+  stroke: #c8c8c8;
+  fill: transparent;
+}
+.shader-view .heaps-scene svg .edge:not(.draft):hover,
+.shader-view .heaps-scene svg .edge:not(.draft).selected {
+  stroke-width: 5;
+  stroke: #72b4ff;
+}
+.shader-view .heaps-scene svg .rect-selection {
+  stroke: rgba(0, 0, 255, 0.7);
+  fill: rgba(70, 70, 116, 0.08);
+}
+.shader-view .tabs {
+  width: 280px;
+  padding-top: 10px;
+  border-left: 1px solid #444444;
+}
+.shader-view .tabs span {
+  margin-left: 5px;
+  font-size: 15px;
+  font-weight: bold;
+}
 /* Golden Layout Fixes */
 .lm_header .lm_tabs {
   z-index: 1;

+ 196 - 0
bin/style.less

@@ -1143,6 +1143,202 @@ input[type=checkbox] {
 	}
 }
 
+/* Shader Editor */
+.shader-view {
+	#add-menu {
+		position: absolute;
+		width: 400px;
+		box-shadow: 0px 0px 25px -4px black;
+
+		background-color: rgb(34, 34, 34);
+		border: 1px solid #444;
+
+		.search-container {
+			display: flex;
+
+			.icon {
+				width: 16px;
+				font-size: 15px;
+				text-align: center;
+				padding: 7px;
+				border: solid #444;
+				background: #2d2d2d;
+				border-width: 0 1px 1px 0;
+			}
+
+			.search-bar {
+				flex: 1;
+				margin: -1px 11px 0px -1px;
+
+				input {
+					width: 100%;
+					height: 30px;
+					padding-left: 10px;
+					outline: none;
+
+					&:focus {
+						border: 1px solid #888888;
+					}
+				}
+			}
+		}
+		#results {
+			max-height: 300px;
+
+    		overflow-y: auto;
+			div {
+				padding: 5px;
+				border-top: 1px solid #444444;
+				cursor: pointer;
+				color: #d4d4d4;
+
+				& {
+					position: relative;
+				}
+
+				&.group {
+					background: #474747;
+					color: #c0c0c0;
+					cursor: default;
+					z-index: 1;
+    				box-shadow: 0px 0px 34px -9px black;
+
+					&:hover {
+						background: #474747!important;
+						color: #dadada!important;
+						box-shadow: none;
+					}
+				}
+
+				& > span:first-child {
+					font-weight: bold;
+					padding-left: 10px;
+				}
+				& > span:last-child {
+					font-style: oblique;
+					padding-left: 7px;
+				}
+				&:first-child {
+					border-top: 0;
+				}
+				&:hover, &.selected {
+					background: #5eb7bf;
+					color: #000000;
+					box-shadow: inset 0px 0px 12px -1px black;
+    				z-index: 3;
+				}
+			}
+		}
+
+	}
+	.heaps-scene {
+		#preview {
+			position: absolute;
+
+			width: 300px;
+			height: 300px;
+
+			background-color: #111;
+   			border: 1px solid #444;
+
+			right: 290px;
+			bottom: 10px;
+		}
+		svg {
+
+			g {
+				stroke: #202020;
+
+				&.selected {
+					.outline {
+						stroke: #668fff;
+					}
+				}
+			}
+			.outline {
+				fill: none;
+			}
+
+			.head-box {
+				fill: #424242;
+			}
+
+			.title-box {
+				fill: #ffffff;
+				stroke: none;
+				user-select: none;
+				&::selection {
+					background: none;
+				}
+			}
+
+			.nodes, .parameters {
+				fill: #5c5c5c;
+				opacity: 0.75;
+			}
+
+			.input-node, .output-node {
+				fill: rgb(200, 200, 200);
+				stroke-width: 15px;
+				stroke-opacity: 0;
+
+				&.nodeMatch {
+					fill: #5ca4ab;
+				}
+			}
+
+			.title-node, .title-param {
+				fill: #ffffff;
+				stroke: none;
+				user-select: none;
+				&::selection {
+					background: none;
+				}
+			}
+
+			.parameters-group {
+				span {
+					color: #ffffff;
+					user-select: none;
+					&::selection {
+						background: none;
+					}
+				}
+				input[type=text], select {
+					width: 100%;
+				}
+			}
+
+			.edge {
+				stroke-width: 2;
+				stroke: rgb(200, 200, 200);
+				fill: transparent;
+			}
+
+			.edge:not(.draft):hover, .edge:not(.draft).selected {
+				stroke-width: 5;
+				stroke: rgb(114, 180, 255);
+			}
+
+			.rect-selection {
+				stroke: rgba(0, 0, 255, 0.7);
+				fill: rgba(70, 70, 116, 0.08);
+			}
+		}
+	}
+	.tabs {
+		width: 280px;
+		padding-top: 10px;
+		border-left: 1px solid #444444;
+
+		span { // title
+			margin-left: 5px;
+			font-size: 15px;
+			font-weight: bold;
+		}
+	}
+}
+
 /* Golden Layout Fixes */
 
 .lm_header .lm_tabs {

+ 8 - 0
hide/comp/SVG.hx

@@ -45,6 +45,14 @@ class SVG extends Component {
 		return make(parent, "line", {x1:x1, y1:y1, x2:x2, y2:y2}, style);
 	}
 
+	public function curve(?parent: Element, x1:Float, y1:Float, x2:Float, y2:Float, xTurn:Float, yTurn:Float, ?style:Dynamic) {
+		return make(parent, "path", {d : 'M ${x1} ${y1} Q ${xTurn} ${yTurn}, ${x1 + (x2-x1)/2} ${y1 + (y2-y1)/2}, T ${x2} ${y2}'}, style);
+	}
+
+	public function foreignObject(?parent: Element, x:Float, y:Float, width:Float, height:Float, ?style:Dynamic) {
+		return make(parent, "foreignObject", {x:x, y:y, width:width, height:height}, style);
+	}
+
 	public function polygon(?parent: Element, points: Array<h2d.col.Point>, ?style:Dynamic) {
 		// TODO: Use https://www.w3schools.com/graphics/svg_polygon.asp
 		var lines = ['M${points[0].x},${points[0].y} '];

+ 924 - 0
hide/view/ShaderEditor.hx

@@ -0,0 +1,924 @@
+package hide.view;
+
+using hxsl.Ast.Type;
+
+import haxe.rtti.Rtti;
+import haxe.rtti.Meta;
+import hxsl.DynamicShader;
+import hxsl.SharedShader;
+import hide.comp.SceneEditor;
+import js.jquery.JQuery;
+import h2d.col.Point;
+import h2d.col.IPoint;
+import hide.comp.SVG;
+import hide.view.shadereditor.Box;
+import hrt.shgraph.ShaderGraph;
+import hrt.shgraph.ShaderNode;
+import hrt.shgraph.ShaderType;
+import hrt.shgraph.ShaderType.SType;
+
+enum EdgeState { None; FromInput; FromOutput; }
+typedef NodeInfo = { name : String, description : String, key : String };
+
+typedef Edge = { from : Box, nodeFrom : JQuery, to : Box, nodeTo : JQuery, elt : JQuery };
+
+class ShaderEditor extends FileView {
+
+	var parent : JQuery;
+	static var editor : SVG;
+	var editorMatrix : JQuery;
+
+
+	var listOfClasses = new Map<String, Array<NodeInfo>>();
+	var addMenu : JQuery;
+	var selectedNode : JQuery;
+
+	var listOfBoxes : Array<Box> = [];
+	var listOfEdges : Array<Edge> = [];
+
+	static var transformMatrix : Array<Float> = [1, 0, 0, 1, 0, 0];
+	var isPanning : Bool = false;
+
+	// used for selection
+	var listOfBoxesSelected : Array<Box> = [];
+	var recSelection : JQuery;
+	var startRecSelection : h2d.col.IPoint;
+	var firstClickDrag : h2d.col.IPoint;
+
+	// used to build edge
+	static var NODE_TRIGGER_NEAR = 2000.0;
+	var isCreatingLink : EdgeState = None;
+	var startLinkBox : Box;
+	var endLinkBox : Box;
+	var startLinkGrNode : JQuery;
+	var endLinkNode : JQuery;
+	var currentLink : JQuery; // draft of edge
+
+	// used for deleting
+	var currentEdge : Edge;
+
+	// used to preview
+	var sceneEditor : SceneEditor;
+
+	var root : hxd.prefab.Prefab;
+	var obj : h3d.scene.Object;
+	var plight : hxd.prefab.Prefab;
+	var light : h3d.scene.Object;
+	var lightDirection : h3d.Vector;
+
+	static var shaderGraph : ShaderGraph;
+
+	var shaderGenerated : DynamicShader;
+
+	override function onDisplay() {
+		shaderGraph = new ShaderGraph(getPath());
+		addMenu = null;
+		element.html('
+			<div class="flex vertical">
+				<div class="flex-elt shader-view">
+					<div class="heaps-scene">
+					</div>
+					<div class="tabs">
+						<span>Parameters</span>
+						<div class="tab expand" name="Scene" icon="sitemap">
+							<div class="hide-block" style="height:50%">
+								<div class="hide-scene-tree hide-list">
+								</div>
+							</div>
+							<div class="hide-scroll"></div>
+						</div>
+					</div>
+				</div>
+			</div>');
+		parent = element.find(".heaps-scene");
+		editor = new SVG(parent);
+		var preview = new Element('<div id="preview" ></div>');
+		preview.on("mousedown", function(e) { e.stopPropagation(); });
+		preview.on("wheel", function(e) { e.stopPropagation(); });
+		parent.append(preview);
+
+		var def = new hxd.prefab.Library();
+		new hrt.prefab.RenderProps(def).name = "renderer";
+		var l = new hrt.prefab.Light(def);
+		l.name = "sunLight";
+		l.kind = Directional;
+		l.power = 1.5;
+		var q = new h3d.Quat();
+		q.initDirection(new h3d.Vector(-1,-1.5,-3));
+		var a = q.toEuler();
+		l.rotationX = Math.round(a.x * 180 / Math.PI);
+		l.rotationY = Math.round(a.y * 180 / Math.PI);
+		l.rotationZ = Math.round(a.z * 180 / Math.PI);
+		l.shadows.mode = Dynamic;
+		l.shadows.size = 1024;
+		root = def;
+
+		sceneEditor = new hide.comp.SceneEditor(this, root);
+		sceneEditor.editorDisplay = false;
+		sceneEditor.onRefresh = onRefresh;
+		sceneEditor.onUpdate = update;
+		sceneEditor.view.keys = new hide.ui.Keys(null); // Remove SceneEditor Shortcuts
+		sceneEditor.view.keys.register("save", function() {
+			save();
+			skipNextChange = true;
+			modified = false;
+		});
+
+		editorMatrix = editor.group(editor.element);
+
+		// rectangle Selection
+		parent.on("mousedown", function(e) {
+
+			closeAddMenu();
+
+			if (e.button == 0) {
+				startRecSelection = new IPoint(e.offsetX, e.offsetY);
+				if (currentEdge != null) {
+					currentEdge.elt.removeClass("selected");
+					currentEdge = null;
+				}
+
+				clearSelectionBoxes();
+			}
+			if (e.button == 1) {
+				firstClickDrag = new IPoint(e.clientX, e.clientY);
+				isPanning = true;
+			}
+		});
+
+		parent.on("mousemove", function(e : js.jquery.Event) {
+			e.preventDefault();
+			e.cancelBubble=true;
+    		e.returnValue=false;
+
+			if (isCreatingLink != None) {
+				createLink(e);
+				return;
+			}
+			// Moving edge
+			if (currentEdge != null) {
+				// TODO: handle moving edge => disconnect closest node
+				// try to use the same code when user clicks on input node already connected and here
+			}
+			if (isPanning) {
+				pan(new Point(e.clientX - firstClickDrag.x, e.clientY - firstClickDrag.y));
+				firstClickDrag.x = e.clientX;
+				firstClickDrag.y = e.clientY;
+				return;
+			}
+			// Edit rectangle selection
+			if (startRecSelection != null) {
+				var endRecSelection = new h2d.col.IPoint(e.offsetX, e.offsetY);
+				var xMin = startRecSelection.x;
+				var xMax = endRecSelection.x;
+				var yMin = startRecSelection.y;
+				var yMax = endRecSelection.y;
+
+				if (startRecSelection.x > endRecSelection.x) {
+					xMin = endRecSelection.x;
+					xMax = startRecSelection.x;
+				}
+				if (startRecSelection.y > endRecSelection.y) {
+					yMin = endRecSelection.y;
+					yMax = startRecSelection.y;
+				}
+
+				if (recSelection != null) recSelection.remove();
+				recSelection = editor.rect(editor.element, xMin, yMin, xMax - xMin, yMax - yMin).addClass("rect-selection");
+
+				for (box in listOfBoxes) {
+					if (isInside(box, new IPoint(xMin, yMin), new IPoint(xMax, yMax))) {
+						box.setSelected(true);
+					} else {
+						box.setSelected(false);
+					}
+				}
+				return;
+			}
+
+			// Move selected boxes
+			if (listOfBoxesSelected.length > 0 && firstClickDrag != null) {
+				var dx = (e.clientX - firstClickDrag.x)/transformMatrix[0];
+				var dy = (e.clientY - firstClickDrag.y)/transformMatrix[3];
+
+				for (b in listOfBoxesSelected) {
+					b.setPosition(b.getX() + dx, b.getY() + dy);
+					shaderGraph.setPosition(b.getId(), b.getX(), b.getY());
+					// move edges from and to this box
+					for (edge in listOfEdges) {
+						if (edge.from == b || edge.to == b) {
+							edge.elt.remove();
+							edge.elt = createCurve(edge.nodeFrom, edge.nodeTo);
+
+							edge.elt.on("mousedown", function(e) {
+								e.stopPropagation();
+								clearSelectionBoxes();
+								this.currentEdge = edge;
+								currentEdge.elt.addClass("selected");
+							});
+						}
+					}
+				}
+				firstClickDrag.x = e.clientX;
+				firstClickDrag.y = e.clientY;
+				return;
+			}
+		});
+
+		parent.on("mouseup", function(e) {
+			if (e.button == 0) {
+				// Stop link creation
+				if (isCreatingLink != None) {
+					if (startLinkBox != null && endLinkBox != null) {
+						createEdgeInShaderGraph();
+					} else {
+						if (currentLink != null) currentLink.remove();
+						currentLink = null;
+					}
+					startLinkBox = endLinkBox = null;
+					startLinkGrNode = endLinkNode = null;
+					isCreatingLink = None;
+					clearAvailableNodes();
+				}
+
+				// Stop rectangle selection
+				firstClickDrag = null;
+				startRecSelection = null;
+				if (recSelection != null) {
+					recSelection.remove();
+					recSelection = null;
+					for (b in listOfBoxes)
+						if (b.selected)
+							listOfBoxesSelected.push(b);
+				}
+			}
+
+			// Stop panning
+			if (e.button == 1) {
+				firstClickDrag = null;
+				isPanning = false;
+			}
+		});
+
+		// Zoom control
+		parent.on("wheel", function(e) {
+			if (e.originalEvent.deltaY < 0) {
+				zoom(1.1, e.clientX, e.clientY);
+			} else {
+				zoom(0.9, e.clientX, e.clientY);
+			}
+		});
+
+		new Element("body").on("keydown", function(e) {
+
+			if (e.shiftKey && e.keyCode != 16) {
+				openAddMenu();
+
+				return;
+			}
+
+			if (e.keyCode == 46) {
+				if (currentEdge != null) {
+					removeEdge(currentEdge);
+				}
+				if (listOfBoxesSelected.length > 0) {
+					for (b in listOfBoxesSelected) {
+						removeBox(b);
+					}
+					clearSelectionBoxes();
+				}
+			} else if (e.keyCode == 32) {
+				var s = new SharedShader("");
+				s.data = shaderGraph.buildFragment();
+				s.initialize();
+
+				if (shaderGenerated != null)
+					for (m in obj.getMaterials())
+						m.mainPass.removeShader(shaderGenerated);
+
+				shaderGenerated = new DynamicShader(s);
+				for (m in obj.getMaterials()) {
+					m.mainPass.addShader(shaderGenerated);
+				}
+			} else if (e.keyCode == 83 && e.ctrlKey) { // CTRL+S : save
+				shaderGraph.save();
+			}
+		});
+
+		var mapOfNodes = ShaderNode.registeredNodes;
+		for (key in mapOfNodes.keys()) {
+			var metas = haxe.rtti.Meta.getType(mapOfNodes[key]);
+			if (metas.group == null) {
+				continue;
+			}
+			var group = metas.group[0];
+
+			if (listOfClasses[group] == null)
+				listOfClasses[group] = new Array<NodeInfo>();
+
+			listOfClasses[group].push({ name : (metas.name != null) ? metas.name[0] : key , description : (metas.description != null) ? metas.description[0] : "" , key : key });
+		}
+
+		for (key in listOfClasses.keys()) {
+			listOfClasses[key].sort(function (a, b): Int {
+				if (a.name < b.name) return -1;
+				else if (a.name > b.name) return 1;
+				return 0;
+			});
+		}
+
+		new Element("body").ready(function(e) {
+			for (node in shaderGraph.getNodes()) {
+				addBox(new Point(node.x, node.y), std.Type.getClass(node.instance), node.instance);
+			}
+
+			for (box in listOfBoxes) {
+				for (key in box.getShaderNode().getInputsKey()) {
+					var input = box.getShaderNode().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 update(dt : Float) {
+
+	}
+
+	function onRefresh() {
+
+		plight = root.getAll(hrt.prefab.Light)[0];
+		if( plight != null ) {
+			this.light = sceneEditor.context.shared.contexts.get(plight).local3d;
+			lightDirection = this.light.getDirection();
+		}
+
+		var sphere = new h3d.prim.Sphere(1, 32, 32);
+		sphere.addNormals();
+		var mesh = new h3d.scene.Mesh(sphere, sceneEditor.scene.s3d);
+		mesh.material.mainPass.enableLights = true;
+		mesh.material.shadows = true;
+		mesh.material.color.load(new h3d.Vector(1, 1, 1));
+		obj = mesh;
+
+		element.find("#preview").first().append(sceneEditor.scene.element);
+	}
+
+	function addNode(p : Point, nodeClass : Class<ShaderNode>) {
+		var node = shaderGraph.addNode(p.x, p.y, nodeClass);
+
+		addBox(p, nodeClass, node);
+	}
+
+	function addBox(p : Point, nodeClass : Class<ShaderNode>, node : ShaderNode) {
+
+		var className = std.Type.getClassName(nodeClass);
+		className = className.substr(className.lastIndexOf(".") + 1);
+
+		var box = new Box(editor, editorMatrix, p.x, p.y, node);
+		var elt = box.getElement();
+		elt.mousedown(function(e) {
+			if (e.button != 0)
+				return;
+			e.stopPropagation();
+
+			firstClickDrag = new IPoint(e.clientX, e.clientY);
+			if (!box.selected) {
+				if (!e.ctrlKey) {
+					// when not group selection and click on box not selected
+					clearSelectionBoxes();
+					listOfBoxesSelected = [box];
+				} else
+					listOfBoxesSelected.push(box);
+				box.setSelected(true);
+			}
+		});
+		elt.mouseup(function(e) {
+			if (e.button != 0)
+				return;
+			firstClickDrag = null;
+			if (listOfBoxesSelected.length == 1 && box.selected && !e.ctrlKey) {
+				clearSelectionBoxes();
+			}
+		});
+		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;
+			}
+			if (Reflect.hasField(m, "input")) {
+				var name : String = (m.input != null && m.input.length > 0) ? Reflect.field(m, "input")[0] : "input";
+				var grNode = box.addInput(editor, name);
+				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) {
+						isCreatingLink = FromOutput;
+						for (edge in listOfEdges) {
+							if (edge.nodeTo.is(node)) {
+								startLinkGrNode = edge.nodeFrom.parent();
+								startLinkBox = edge.from;
+								setAvailableInputNodes(edge.from, edge.nodeFrom.attr("field"));
+								removeEdge(edge);
+								createLink(e);
+								return;
+							}
+						}
+					}
+					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"));
+				});
+			}
+		}
+		box.generateParameters(editor);
+	}
+
+	function removeBox(box : Box) {
+		var length = listOfEdges.length;
+		for (i in 0...length) {
+			var edge = listOfEdges[length-i-1];
+			if (edge.from == box || edge.to == box) {
+				removeEdge(edge); // remove edge from listOfEdges
+			}
+		}
+		box.dispose();
+		listOfBoxes.remove(box);
+		shaderGraph.removeNode(box.getId());
+	}
+
+	function removeEdge(edge : Edge) {
+		edge.elt.remove();
+		edge.nodeTo.removeAttr("hasLink");
+		shaderGraph.removeEdge(edge.to.getId(), edge.nodeTo.attr("field"));
+		listOfEdges.remove(edge);
+	}
+
+	function setAvailableInputNodes(boxOutput : Box, field : String) {
+		var type = boxOutput.getShaderNode().getOutputType(field);
+		var sType : SType;
+		if (type == null) {
+			sType = boxOutput.getShaderNode().getOutputInfo(field);
+		} else {
+			sType = ShaderType.getType(type);
+		}
+
+		for (box in listOfBoxes) {
+			for (input in box.inputs) {
+				if (box.getShaderNode().checkTypeAndCompatibilyInput(input.attr("field"), sType)) {
+					input.addClass("nodeMatch");
+				}
+			}
+		}
+	}
+
+	function setAvailableOutputNodes(boxInput : Box, field : String) {
+		for (box in listOfBoxes) {
+			for (output in box.outputs) {
+				var outputField = output.attr("field");
+				var type = box.getShaderNode().getOutputType(outputField);
+				var sType : SType;
+				if (type == null) {
+					sType = box.getShaderNode().getOutputInfo(outputField);
+				} else {
+					sType = ShaderType.getType(type);
+				}
+				if (boxInput.getShaderNode().checkTypeAndCompatibilyInput(field, sType)) {
+					output.addClass("nodeMatch");
+				}
+			}
+		}
+	}
+
+	function clearAvailableNodes() {
+		editor.element.find(".nodeMatch").removeClass("nodeMatch");
+	}
+
+	function createEdgeInShaderGraph() {
+		var startLinkNode = startLinkGrNode.find(".node");
+		if (isCreatingLink == FromInput) {
+			var tmpBox = startLinkBox;
+			startLinkBox = endLinkBox;
+			endLinkBox = tmpBox;
+
+			var tmpNode = startLinkNode;
+			startLinkNode = endLinkNode;
+			endLinkNode = tmpNode;
+		}
+
+		var edge = { from: startLinkBox, nodeFrom : startLinkNode, to : endLinkBox, nodeTo : endLinkNode, elt : currentLink };
+		if (endLinkNode.attr("hasLink") != null) {
+			var length = listOfEdges.length;
+			for (i in 0...length) {
+				var e = listOfEdges[length-i-1];
+				if (e.to == edge.to) {
+					removeEdge(e);
+				}
+			}
+		}
+		shaderGraph.addEdge({ idOutput: startLinkBox.getId(), nameOutput: startLinkNode.attr("field"), idInput: endLinkBox.getId(), nameInput: endLinkNode.attr("field") });
+		createEdgeInEditorGraph(edge);
+		currentLink.removeClass("draft");
+		currentLink = null;
+	}
+
+	function createEdgeInEditorGraph(edge) {
+		listOfEdges.push(edge);
+		edge.nodeTo.attr("hasLink", "true");
+
+		edge.elt.on("mousedown", function(e) {
+			e.stopPropagation();
+			clearSelectionBoxes();
+			this.currentEdge = edge;
+			currentEdge.elt.addClass("selected");
+		});
+	}
+
+	function createLink(e : js.jquery.Event) {
+
+		var nearestNode = null;
+		var minDistNode = NODE_TRIGGER_NEAR;
+
+		// checking nearest box
+		var nearestBox = listOfBoxes[0];
+		var minDist = distanceToBox(nearestBox, e.clientX, e.clientY);
+		for (i in 1...listOfBoxes.length) {
+			var tmpDist = distanceToBox(listOfBoxes[i], e.clientX, e.clientY);
+			if (tmpDist < minDist) {
+				minDist = tmpDist;
+				nearestBox = listOfBoxes[i];
+			}
+		}
+
+		// checking nearest node in the nearest box
+		if (isCreatingLink == FromInput) {
+			var startIndex = 0;
+			while (startIndex < nearestBox.outputs.length && !nearestBox.outputs[startIndex].hasClass("nodeMatch")) {
+				startIndex++;
+			}
+			if (startIndex < nearestBox.outputs.length) {
+				nearestNode = nearestBox.outputs[startIndex];
+				minDistNode = distanceToElement(nearestNode, e.clientX, e.clientY);
+				for (i in startIndex+1...nearestBox.outputs.length) {
+					if (!nearestBox.outputs[i].hasClass("nodeMatch"))
+						continue;
+					var tmpDist = distanceToElement(nearestBox.outputs[i], e.clientX, e.clientY);
+					if (tmpDist < minDistNode) {
+						minDistNode = tmpDist;
+						nearestNode = nearestBox.outputs[i];
+					}
+				}
+			}
+		} else {
+			// input has one edge at most
+			var startIndex = 0;
+			while (startIndex < nearestBox.inputs.length && !nearestBox.inputs[startIndex].hasClass("nodeMatch")) {
+				startIndex++;
+			}
+			if (startIndex < nearestBox.inputs.length) {
+				nearestNode = nearestBox.inputs[startIndex];
+				minDistNode = distanceToElement(nearestNode, e.clientX, e.clientY);
+				for (i in startIndex+1...nearestBox.inputs.length) {
+					if (!nearestBox.inputs[i].hasClass("nodeMatch"))
+						continue;
+					var tmpDist = distanceToElement(nearestBox.inputs[i], e.clientX, e.clientY);
+					if (tmpDist < minDistNode) {
+						minDistNode = tmpDist;
+						nearestNode = nearestBox.inputs[i];
+					}
+				}
+			}
+		}
+		if (minDistNode < NODE_TRIGGER_NEAR) {
+			endLinkNode = nearestNode;
+			endLinkBox = nearestBox;
+		} else {
+			endLinkNode = null;
+			endLinkBox = null;
+		}
+
+		// create edge
+		if (currentLink != null) currentLink.remove();
+		currentLink = createCurve(startLinkGrNode.find(".node"), nearestNode, minDistNode, e.clientX, e.clientY, true);
+
+	}
+
+	function createCurve(start : JQuery, end : JQuery, ?distance : Float, ?x : Float, ?y : Float, ?isDraft : Bool) {
+		var offsetEnd;
+		var offsetStart = start.offset();
+		if (distance == null || distance < NODE_TRIGGER_NEAR) {
+			offsetEnd = end.offset();
+		} else {
+			offsetEnd = { top : y, left : x };
+		}
+
+		if (isCreatingLink == FromInput) {
+			var tmp = offsetStart;
+			offsetStart = offsetEnd;
+			offsetEnd = tmp;
+		}
+		var startX = lX(offsetStart.left) + Box.NODE_RADIUS;
+		var startY = lY(offsetStart.top) + Box.NODE_RADIUS;
+		var diffDistanceY = offsetEnd.top - offsetStart.top;
+		var signCurveY = ((diffDistanceY > 0) ? -1 : 1);
+		diffDistanceY = Math.abs(diffDistanceY);
+		var valueCurveX = 100;
+		var valueCurveY = 1;
+		var maxDistanceY = 900;
+
+		var curve = editor.curve(null,
+							startX,
+							startY,
+							lX(offsetEnd.left) + Box.NODE_RADIUS,
+							lY(offsetEnd.top) + Box.NODE_RADIUS,
+							startX + valueCurveX * (Math.min(maxDistanceY, diffDistanceY)/maxDistanceY),
+							startY + signCurveY * valueCurveY * (Math.min(maxDistanceY, diffDistanceY)/maxDistanceY))
+							.addClass("edge");
+		editorMatrix.prepend(curve);
+		if (isDraft)
+			curve.addClass("draft");
+
+		return curve;
+	}
+
+	function openAddMenu() {
+		if (addMenu != null) {
+			var input = addMenu.find("#search-input");
+			input.val("");
+			addMenu.show();
+			input.focus();
+			var posCursor = new IPoint(Std.int(ide.mouseX - parent.offset().left), Std.int(ide.mouseY - parent.offset().top));
+
+			addMenu.css("left", posCursor.x);
+			addMenu.css("top", posCursor.y);
+			return;
+		}
+
+		addMenu = new Element('
+		<div id="add-menu">
+			<div class="search-container">
+				<div class="icon" >
+					<i class="fa fa-search"></i>
+				</div>
+				<div class="search-bar" >
+					<input type="text" id="search-input" >
+				</div>
+			</div>
+			<div id="results">
+			</div>
+		</div>').appendTo(parent);
+
+		var posCursor = new IPoint(Std.int(ide.mouseX - parent.offset().left), Std.int(ide.mouseY - parent.offset().top));
+
+		addMenu.css("left", posCursor.x);
+		addMenu.css("top", posCursor.y);
+
+		addMenu.on("mousedown", function(e) {
+			e.stopPropagation();
+		});
+
+		var results = addMenu.find("#results");
+		results.on("wheel", function(e) {
+			e.stopPropagation();
+		});
+
+		var keys = listOfClasses.keys();
+		var sortedKeys = [];
+		for (k in keys) {
+			sortedKeys.push(k);
+		}
+		sortedKeys.sort(function (a, b) {
+			if (a < b) return -1;
+			if (a > b) return 1;
+			return 0;
+		});
+
+		for (key in sortedKeys) {
+			new Element('
+				<div class="group" >
+					<span> ${key} </span>
+				</div>').appendTo(results);
+			for (node in listOfClasses[key]) {
+				new Element('
+					<div node="${node.key}" >
+						<span> ${node.name} </span> <span> ${node.description} </span>
+					</div>').appendTo(results);
+			}
+		}
+
+		var input = addMenu.find("#search-input");
+		input.focus();
+		var divs = new Element("#results > div");
+		input.on("keydown", function(ev) {
+			if (ev.keyCode == 38 || ev.keyCode == 40) {
+				ev.stopPropagation();
+				ev.preventDefault();
+
+				if (selectedNode != null)
+					this.selectedNode.removeClass("selected");
+
+				var selector = "div[node]:not([style*='display: none'])";
+				var elt = this.selectedNode;
+
+				if (ev.keyCode == 38) {
+					do {
+						elt = elt.prev();
+					} while (elt.length > 0 && !elt.is(selector));
+				} else if (ev.keyCode == 40) {
+					do {
+						elt = elt.next();
+					} while (elt.length > 0 && !elt.is(selector));
+				}
+				if (elt.length == 1) {
+					this.selectedNode = elt;
+				}
+				if (this.selectedNode != null)
+					this.selectedNode.addClass("selected");
+
+				var offsetDiff = this.selectedNode.offset().top - results.offset().top;
+				if (offsetDiff > 225) {
+					results.scrollTop((offsetDiff-225)+results.scrollTop());
+				} else if (offsetDiff < 35) {
+					results.scrollTop(results.scrollTop()-(35-offsetDiff));
+				}
+			}
+		});
+		input.on("keyup", function(ev) {
+			if (ev.keyCode == 38 || ev.keyCode == 40) {
+				return;
+			}
+
+			if (ev.keyCode == 13) {
+				var key = this.selectedNode.attr("node");
+				var posCursor = new Point(ide.mouseX - parent.offset().left - 25, ide.mouseY - parent.offset().top - 10);
+				addNode(posCursor, ShaderNode.registeredNodes[key]);
+				closeAddMenu();
+			} else {
+				if (this.selectedNode != null)
+					this.selectedNode.removeClass("selected");
+				var value = input.val();
+				var children = divs.elements();
+				var isFirst = true;
+				var lastGroup = null;
+				for (elt in children) {
+					if (elt.hasClass("group")) {
+						lastGroup = elt;
+						elt.hide();
+						continue;
+					}
+					if (elt.children().first().html().toLowerCase().indexOf(value.toLowerCase()) != -1) {
+						if (isFirst) {
+							this.selectedNode = elt;
+							isFirst = false;
+						}
+						elt.show();
+						if (lastGroup != null)
+							lastGroup.show();
+					} else {
+						elt.hide();
+					}
+				}
+				if (this.selectedNode != null)
+					this.selectedNode.addClass("selected");
+			}
+		});
+		divs.mouseover(function(ev) {
+			if (ev.getThis().hasClass("group")) {
+				return;
+			}
+			this.selectedNode.removeClass("selected");
+			this.selectedNode = ev.getThis();
+			this.selectedNode.addClass("selected");
+		});
+		divs.mouseup(function(ev) {
+			if (ev.getThis().hasClass("group")) {
+				return;
+			}
+			var key = ev.getThis().attr("node");
+			var posCursor = new Point(Std.int(ide.mouseX - parent.offset().left - 25), Std.int(ide.mouseY - parent.offset().top - 10));
+			addNode(posCursor, ShaderNode.registeredNodes[key]);
+			closeAddMenu();
+		});
+	}
+
+	function closeAddMenu() {
+		if (addMenu != null)
+			addMenu.hide();
+	}
+
+	function clearSelectionBoxes() {
+		for(b in listOfBoxesSelected) b.setSelected(false);
+		listOfBoxesSelected = [];
+		if (this.currentEdge != null) {
+			currentEdge.elt.removeClass("selected");
+		}
+	}
+
+	function updateMatrix() {
+		editorMatrix.attr({transform: 'matrix(${transformMatrix.join(' ')})'});
+	}
+
+	function zoom(scale : Float, x : Int, y : Int) {
+		if (scale > 1 && transformMatrix[0] > 1.2) {
+			return;
+		}
+
+		transformMatrix[0] *= scale;
+		transformMatrix[3] *= scale;
+
+		x -= Std.int(editor.element.offset().left);
+		y -= Std.int(editor.element.offset().top);
+
+		transformMatrix[4] = x - (x - transformMatrix[4]) * scale;
+		transformMatrix[5] = y - (y - transformMatrix[5]) * scale;
+
+		updateMatrix();
+	}
+
+	function pan(p : Point) {
+		transformMatrix[4] += p.x;
+		transformMatrix[5] += p.y;
+
+		updateMatrix();
+	}
+
+	// Useful method
+	function isInside(b : Box, min : IPoint, max : IPoint) {
+		if (max.x < gX(b.getX()) || min.x > gX(b.getX() + b.getWidth()))
+			return false;
+		if (max.y < gY(b.getY()) || min.y > gY(b.getY() + b.getHeight()))
+			return false;
+
+		return true;
+	}
+	function distanceToBox(b : Box, x : Int, y : Int) {
+		var dx = Math.max(Math.abs(lX(x) - (b.getX() + (b.getWidth() / 2))) - b.getWidth() / 2, 0);
+		var dy = Math.max(Math.abs(lY(y) - (b.getY() + (b.getHeight() / 2))) - b.getHeight() / 2, 0);
+		return dx * dx + dy * dy;
+	}
+	function distanceToElement(element : JQuery, x : Int, y : Int) {
+		if (element == null)
+			return NODE_TRIGGER_NEAR+1;
+		var dx = Math.max(Math.abs(x - (element.offset().left + element.width() / 2)) - element.width() / 2, 0);
+		var dy = Math.max(Math.abs(y - (element.offset().top + element.height() / 2)) - element.height() / 2, 0);
+		return dx * dx + dy * dy;
+	}
+	static function gX(x : Float) : Float {
+		return x*transformMatrix[0] + transformMatrix[4];
+	}
+	static function gY(y : Float) : Float {
+		return y*transformMatrix[3] + transformMatrix[5];
+	}
+	static function gPos(x : Float, y : Float) : Point {
+		return new Point(gX(x), gY(y));
+	}
+	static function lX(x : Float) : Float {
+		var screenOffset = editor.element.offset();
+		x -= screenOffset.left;
+		return (x - transformMatrix[4])/transformMatrix[0];
+	}
+	static function lY(y : Float) : Float {
+		var screenOffset = editor.element.offset();
+		y -= screenOffset.top;
+		return (y - transformMatrix[5])/transformMatrix[3];
+	}
+	static function lPos(x : Float, y : Float) : Point {
+		return new Point(lX(x), lY(y));
+	}
+
+	static var _ = FileTree.registerExtension(ShaderEditor,["shader"],{ icon : "scribd" });
+
+}

+ 181 - 0
hide/view/shadereditor/Box.hx

@@ -0,0 +1,181 @@
+package hide.view.shadereditor;
+
+import hide.comp.SVG;
+import js.jquery.JQuery;
+import hrt.shgraph.ShaderNode;
+
+class Box {
+
+	var nodeInstance : ShaderNode;
+
+	var x : Float;
+	var y : Float;
+
+	var width : Int = 150;
+	var height : Int;
+	var paramHeight : Int = 0;
+
+	var HEADER_HEIGHT = 27;
+	@const var NODE_MARGIN = 20;
+	public static var NODE_RADIUS = 5;
+	@const var NODE_TITLE_PADDING = 10;
+	public var selected : Bool = false;
+
+	public var inputs : Array<JQuery> = [];
+	public var outputs : Array<JQuery> = [];
+
+	var element : JQuery;
+	var parametersGroup : JQuery;
+
+	public function new(editor : SVG, parent : JQuery, x : Float, y : Float, node : ShaderNode) {
+		this.nodeInstance = node;
+
+		var metas = haxe.rtti.Meta.getType(Type.getClass(node));
+		if (metas.width != null) {
+			this.width = metas.width[0];
+		}
+		var className = (metas.name != null) ? metas.name[0] : "Undefined";
+
+		element = editor.group(parent).addClass("not-selected");
+		setPosition(x, y);
+
+		// outline of box
+		editor.rect(element, -1, -1, width+2, getHeight()+2).addClass("outline");
+
+		// header
+
+		if (Reflect.hasField(metas, "noheader")) {
+			HEADER_HEIGHT = 0;
+		} else {
+			editor.rect(element, 0, 0, this.width, HEADER_HEIGHT).addClass("head-box");
+			editor.text(element, 10, HEADER_HEIGHT-8, className).addClass("title-box");
+		}
+
+		parametersGroup = editor.group(element).addClass("parameters-group");
+
+		// nodes div
+		editor.rect(element, 0, HEADER_HEIGHT, this.width, 0).addClass("nodes");
+		editor.line(element, width/2, HEADER_HEIGHT, width/2, 0, {display: "none"}).addClass("nodes-separator");
+	}
+
+	public function addInput(editor : SVG, name : String) {
+		var node = editor.group(element).addClass("input-node-group");
+		var nodeHeight = HEADER_HEIGHT + (NODE_MARGIN + NODE_RADIUS) * (inputs.length+1);
+		var nodeCircle = editor.circle(node, 0, nodeHeight, NODE_RADIUS).addClass("node input-node");
+
+		if (name.length > 0)
+			editor.text(node, NODE_TITLE_PADDING, nodeHeight + 4, name).addClass("title-node");
+
+		inputs.push(nodeCircle);
+		refreshHeight();
+
+		return node;
+	}
+
+	public function addOutput(editor : SVG, name : String) {
+		var node = editor.group(element).addClass("output-node-group");
+		var nodeHeight = HEADER_HEIGHT + (NODE_MARGIN + NODE_RADIUS) * (outputs.length+1);
+		var nodeCircle = editor.circle(node, width, nodeHeight, NODE_RADIUS).addClass("node output-node");
+
+		if (name.length > 0)
+			editor.text(node, width - NODE_TITLE_PADDING - (name.length * 6.75), nodeHeight + 4, name).addClass("title-node");
+
+		outputs.push(nodeCircle);
+
+		refreshHeight();
+		return node;
+	}
+
+	public function generateParameters(editor : SVG) {
+		var params = nodeInstance.getParametersHTML(this.width);
+
+		if (params.length == 0) return;
+
+		if (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("");
+		}
+
+			// create param box
+		editor.rect(parametersGroup, 0, 0, this.width, 0).addClass("parameters");
+		paramHeight = 10;
+
+		for (p in params) {
+			var param = editor.group(parametersGroup).addClass("param-group");
+			param.attr("transform", 'translate(0, ${paramHeight})');
+
+			var paramWidth = (p.width() > 0 ? p.width() : this.width);
+			var fObject = editor.foreignObject(param, (this.width - paramWidth) / 2, 5, paramWidth, p.height());
+			p.appendTo(fObject);
+			paramHeight += Std.int(p.height()) + 10;
+		}
+
+		refreshHeight();
+	}
+
+	public function dispose() {
+		element.remove();
+	}
+
+	function refreshHeight() {
+		var height = getNodesHeight();
+		element.find(".nodes").height(height);
+		element.find(".outline").attr("height", getHeight()+2);
+		if (inputs.length > 1 || outputs.length > 1 || paramHeight == 0) {
+			element.find(".nodes-separator").attr("y2", HEADER_HEIGHT + height);
+			element.find(".nodes-separator").show();
+		} else {
+			element.find(".nodes-separator").hide();
+		}
+
+		if (parametersGroup != null) {
+			parametersGroup.attr("transform", 'translate(0, ${HEADER_HEIGHT + height})');
+			parametersGroup.find(".parameters").attr("height", paramHeight);
+		}
+	}
+
+	public function setPosition(x : Float, y : Float) {
+		this.x = x;
+		this.y = y;
+		element.attr({transform: 'translate(${x} ${y})'});
+	}
+
+	public function setSelected(b : Bool) {
+		selected = b;
+		element.removeClass();
+		if (b) {
+			element.addClass("selected");
+		} else {
+			element.addClass("not-selected");
+		}
+	}
+	public function getId() {
+		return this.nodeInstance.id;
+	}
+	public function getShaderNode() {
+		return this.nodeInstance;
+	}
+	public function getX() {
+		return this.x;
+	}
+	public function getY() {
+		return this.y;
+	}
+	public function getWidth() {
+		return this.width;
+	}
+	public function getNodesHeight() {
+		var maxNb = Std.int(Math.max(inputs.length, outputs.length));
+		if (maxNb == 1 && paramHeight > 0) {
+			return 0;
+		}
+		return (NODE_MARGIN + NODE_RADIUS) * (maxNb+1);
+	}
+	public function getHeight() {
+		return HEADER_HEIGHT + getNodesHeight() + paramHeight;
+	}
+	public function getElement() {
+		return element;
+	}
+}

+ 247 - 0
hrt/shgraph/NodeVar.hx

@@ -0,0 +1,247 @@
+package hrt.shgraph;
+
+using hxsl.Ast;
+
+class NodeVar {
+
+	public var node : ShaderNode;
+	public var keyOutput : String;
+
+	public function new ( n : ShaderNode, key : String ) {
+		node = n;
+		keyOutput = key;
+	}
+
+	public function getKey() : String {
+		return keyOutput;
+	}
+
+	public function getTVar() {
+		return node.getOutput(keyOutput);
+	}
+
+	public function getType() : Type {
+		return node.getOutputType(keyOutput);
+	}
+
+	public function getVar(?type: Type) : TExpr {
+		var currentType = getType();
+		if (type == null || currentType == type) {
+			return node.getOutputTExpr(keyOutput);
+		}
+
+		switch(currentType) {
+			case TFloat:
+				var tExprFloat = node.getOutputTExpr(keyOutput);
+				switch(type) {
+					case TVec(size, VFloat):
+						if (size == 2) {
+							return {
+								e: TCall({
+									e: TGlobal(Vec2),
+									p: null,
+									t: TFun([
+										{
+											ret: type,
+											args: [
+											{ name: "u", type : TFloat },
+											{ name: "v", type : TFloat }]
+										}
+									])
+								}, [tExprFloat,
+									tExprFloat]),
+								p: null,
+								t: type
+							};
+						} else if (size == 3) {
+							return {
+								e: TCall({
+									e: TGlobal(Vec3),
+									p: null,
+									t: TFun([
+										{
+											ret: type,
+											args: [
+											{ name: "x", type : TFloat },
+											{ name: "y", type : TFloat },
+											{ name: "z", type : TFloat }]
+										}
+									])
+								}, [tExprFloat,
+									tExprFloat,
+									tExprFloat]),
+								p: null,
+								t: type
+							};
+						} else {
+							return {
+								e: TCall({
+									e: TGlobal(Vec4),
+									p: null,
+									t: TFun([
+										{
+											ret: type,
+											args: [
+											{ name: "r", type : TFloat },
+											{ name: "g", type : TFloat },
+											{ name: "b", type : TFloat },
+											{ name: "a", type : TFloat }]
+										}
+									])
+								}, [tExprFloat,
+									tExprFloat,
+									tExprFloat,
+									{
+										e: TConst(CFloat(1.0)),
+										p: null,
+										t: TFloat
+									}]),
+								p: null,
+								t: type
+							};
+						}
+					default:
+				};
+			case TVec(sizeCurrentType, VFloat):
+				var tExprFloat = node.getOutputTExpr(keyOutput);
+				if (sizeCurrentType == 2) {
+					switch(type) {
+						case TVec(size, VFloat):
+							if (size == 3) {
+								return {
+									e: TCall({
+										e: TGlobal(Vec3),
+										p: null,
+										t: TFun([
+											{
+												ret: type,
+												args: [
+												{ name: "x", type : TFloat },
+												{ name: "y", type : TFloat },
+												{ name: "z", type : TFloat }]
+											}
+										])
+									}, [{
+											e: TSwiz(tExprFloat, [X]),
+											p: null,
+											t: TVec(1, VFloat)
+										},
+										{
+											e: TSwiz(tExprFloat, [Y]),
+											p: null,
+											t: TVec(1, VFloat)
+										},
+										{
+											e: TConst(CFloat(0.0)),
+											p: null,
+											t: TFloat
+										}]),
+									p: null,
+									t: type
+								};
+							} else if (size == 4) {
+								return {
+									e: TCall({
+										e: TGlobal(Vec4),
+										p: null,
+										t: TFun([
+											{
+												ret: type,
+												args: [
+												{ name: "r", type : TFloat },
+												{ name: "g", type : TFloat },
+												{ name: "b", type : TFloat },
+												{ name: "a", type : TFloat }]
+											}
+										])
+									}, [{
+											e: TSwiz(tExprFloat, [X]),
+											p: null,
+											t: TVec(1, VFloat)
+										},
+										{
+											e: TSwiz(tExprFloat, [Y]),
+											p: null,
+											t: TVec(1, VFloat)
+										},
+										{
+											e: TConst(CFloat(0.0)),
+											p: null,
+											t: TFloat
+										},
+										{
+											e: TConst(CFloat(0.0)),
+											p: null,
+											t: TFloat
+										}]),
+									p: null,
+									t: type
+								};
+							}
+						default:
+					};
+				} else if (sizeCurrentType == 3) {
+					switch(type) {
+						case TVec(size, VFloat):
+							if (size == 4) {
+								return {
+									e: TCall({
+										e: TGlobal(Vec4),
+										p: null,
+										t: TFun([
+											{
+												ret: type,
+												args: [
+												{ name: "r", type : TFloat },
+												{ name: "g", type : TFloat },
+												{ name: "b", type : TFloat },
+												{ name: "a", type : TFloat }]
+											}
+										])
+									}, [{
+											e: TSwiz(tExprFloat, [X]),
+											p: null,
+											t: TVec(1, VFloat)
+										},
+										{
+											e: TSwiz(tExprFloat, [Y]),
+											p: null,
+											t: TVec(1, VFloat)
+										},
+										{
+											e: TSwiz(tExprFloat, [Z]),
+											p: null,
+											t: TVec(1, VFloat)
+										},
+										{
+											e: TConst(CFloat(0.0)),
+											p: null,
+											t: TFloat
+										}]),
+									p: null,
+									t: type
+								};
+							}
+						default:
+					};
+				}
+
+			default:
+		}
+		return node.getOutputTExpr(keyOutput);
+	}
+
+	public function getExpr() : Array<TExpr> {
+		if (node.outputCompiled.get(keyOutput) != null)
+			return [];
+		node.outputCompiled.set(keyOutput, true);
+		var res = [];
+		var nodeBuild = node.build(keyOutput);
+		if (getTVar() != null && getTVar().kind == Local)
+			res.push({ e : TVarDecl(getTVar()), t : getType(), p : null });
+		if (nodeBuild != null)
+			res.push(nodeBuild);
+		return res;
+	}
+
+}

+ 49 - 0
hrt/shgraph/Operation.hx

@@ -0,0 +1,49 @@
+package hrt.shgraph;
+
+import hxsl.*;
+
+using hxsl.Ast;
+
+class Operation extends ShaderNode {
+
+	@input("A") var a = SType.Number;
+	@input("B") var b = SType.Number;
+
+	@output() var output = SType.Number;
+
+	var operation : Binop;
+
+	public function new(operation : Binop) {
+		this.operation = operation;
+	}
+
+	override public function createOutputs() {
+		if (a != null && b != null)
+			addOutput("output", a.getVar(b.getType()).t);
+		else if (a != null)
+			addOutput("output", a.getType());
+		else if (b != null)
+			addOutput("output", b.getType());
+		else
+			removeOutput("output");
+	}
+
+	override public function build(key : String) : TExpr {
+
+		return { e: TBinop(OpAssign, {
+						e: TVar(output),
+						p: null,
+						t: output.type
+					}, {
+						e: TBinop(operation,
+							a.getVar(b.getType()),
+							b.getVar(a.getType())),
+						p: null,
+						t: output.type
+					}),
+					p: null,
+					t: output.type
+				};
+	}
+
+}

+ 109 - 0
hrt/shgraph/ParseFieldsMacro.hx

@@ -0,0 +1,109 @@
+package hrt.shgraph;
+
+import hrt.shgraph.ShaderType;
+import haxe.macro.Context;
+import haxe.macro.Expr;
+using haxe.macro.Tools;
+
+typedef SField = { name : String, value : ShaderType.SType };
+
+class ParseFieldsMacro {
+
+#if macro
+
+	public static function build() : Array<Field> {
+		var fields = Context.getBuildFields();
+
+		var mapInputs = new Array<Expr>();
+		var hasInputs = false;
+		var mapOutputs = new Array<Expr>();
+		var hasOutputs = false;
+
+		for ( f in fields ) {
+			if( f.meta == null ) continue;
+			switch (f.kind) {
+				case FVar(t, e):
+					var saveMeta = f.meta;
+					for (m in saveMeta) {
+						if (m.name == "input") {
+							hasInputs = true;
+							var sel = f.name;
+							var get_sel = "get_" + sel;
+							var sfields = macro class {
+								inline function $get_sel() : NodeVar return getInput($v{sel});
+							};
+							for( field in sfields.fields )
+								fields.push(field);
+							if (e == null)
+								Context.error('Input ${sel} has not affectation', f.pos);
+							var enumValue = ["ShaderType", "SType", e.toString().split(".").pop()];
+							mapInputs.push(macro $v{sel} => ${enumValue.toFieldExpr()});
+							f.kind = FProp("get", "null", TPath({ pack: ["hrt", "shgraph"], name: "NodeVar" }));
+							f.meta = saveMeta;
+							break;
+						}
+						if (m.name == "output") {
+							hasOutputs = true;
+							var sel = f.name;
+							var get_sel = "get_" + sel;
+							var sfields = macro class {
+								inline function $get_sel() : TVar return getOutput($v{sel});
+							};
+							for( field in sfields.fields )
+								fields.push(field);
+							if (e == null)
+								Context.error('Output ${sel} has not affectation', f.pos);
+							var enumValue = ["ShaderType", "SType", e.toString().split(".").pop()];
+							mapOutputs.push(macro $v{sel} => ${enumValue.toFieldExpr()});
+							f.kind = FProp("get", "null", TPath({ pack: [], name: "TVar" }));
+							f.meta = saveMeta;
+							break;
+						}
+					}
+				default:
+			}
+		}
+		if (hasInputs) {
+			fields.push({
+				name: "inputsInfo",
+				access: [Access.APrivate],
+				kind: FieldType.FVar(macro:Map<String, ShaderType.SType>, macro $a{mapInputs}),
+				pos: Context.currentPos(),
+			});
+			var sfields = macro class {
+				override public function getInputInfo(key : String) : ShaderType.SType return inputsInfo.get(key);
+			};
+			for( field in sfields.fields )
+				fields.push(field);
+		}
+		if (hasOutputs) {
+			fields.push({
+				name: "outputsInfo",
+				access: [Access.APrivate],
+				kind: FieldType.FVar(macro:Map<String, ShaderType.SType>, macro $a{mapOutputs}),
+				pos: Context.currentPos(),
+			});
+			var sfields = macro class {
+				override public function getOutputInfo(key : String) : ShaderType.SType return outputsInfo.get(key);
+			};
+			for( field in sfields.fields )
+				fields.push(field);
+		}
+
+		var thisClass = Context.getLocalClass();
+		var cl = thisClass.get();
+		var clPath = cl.pack.copy();
+		clPath.push(cl.name);
+
+		fields.push({
+			name: "_",
+			access: [Access.AStatic],
+			kind: FieldType.FVar(macro:Bool, macro ShaderNode.register($v{cl.name}, ${clPath.toFieldExpr()})),
+			pos: Context.currentPos(),
+		});
+
+		return fields;
+	}
+
+#end
+}

+ 16 - 0
hrt/shgraph/ShaderConst.hx

@@ -0,0 +1,16 @@
+package hrt.shgraph;
+
+using hxsl.Ast;
+
+class ShaderConst extends ShaderNode {
+
+	var const : TExpr;
+
+	override public function getOutputType(key : String) : Type {
+		return getOutputTExpr(key).t;
+	}
+
+	override public function build(key : String) : TExpr {
+		return null;
+	}
+}

+ 50 - 0
hrt/shgraph/ShaderFunction.hx

@@ -0,0 +1,50 @@
+package hrt.shgraph;
+
+import hxsl.*;
+
+using hxsl.Ast;
+
+class ShaderFunction extends ShaderNode {
+
+	@output() var output = SType.Variant;
+
+	var func : TGlobal;
+
+	public function new(func : TGlobal) {
+		this.func = func;
+	}
+
+	override public function build(key : String) : TExpr {
+		var args = [];
+		var varArgs = [];
+
+		for (k in getInputsKey()) {
+			args.push({ name: k, type: getInput(k).getType() });
+			varArgs.push(getInput(k).getVar());
+		}
+
+		return {
+					p : null,
+					t : TVec(3,VFloat),
+					e : TBinop(OpAssign, {
+						e: TVar(output),
+						p: null,
+						t: output.type
+					}, {
+						e: TCall({
+							e: TGlobal(func),
+							p: null,
+							t: TFun([
+								{
+									ret: output.type,
+									args: args
+								}
+							])
+						}, varArgs),
+						p: null,
+						t: output.type
+					})
+				};
+	}
+
+}

+ 183 - 0
hrt/shgraph/ShaderGraph.hx

@@ -0,0 +1,183 @@
+package hrt.shgraph;
+
+using hxsl.Ast;
+
+private typedef Node = {
+	x : Float,
+	y : Float,
+	comment : String,
+	id : Int,
+	type : String,
+	?parameters : Dynamic,
+	?instance : ShaderNode
+};
+
+private typedef Edge = {
+	idOutput : Int,
+	nameOutput : String,
+	idInput : Int,
+	nameInput : String
+};
+
+class ShaderGraph {
+
+	var id = 0;
+	var filepath : String;
+	var nodes : Map<Int, Node> = [];
+	var allVariables : Array<TVar> = [];
+
+	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));
+		} catch( e : Dynamic ) {
+			throw "Invalid shader graph parsing ("+e+")";
+		}
+
+		generate(Reflect.getProperty(json, "nodes"), Reflect.getProperty(json, "edges"));
+
+	}
+
+	public function generate(nodes : Array<Node>, edges : Array<Edge>) {
+
+		for (n in nodes) {
+			n.instance = std.Type.createInstance(std.Type.resolveClass(n.type), []);
+			n.instance.loadParameters(n.parameters);
+			n.instance.setId(n.id);
+			this.nodes.set(n.id, n);
+		}
+		if (nodes[nodes.length-1] != null)
+			this.id = nodes[nodes.length-1].id+1;
+
+		for (e in edges) {
+			addEdge(e);
+		}
+	}
+
+	public function addNode(x : Float, y : Float, nameClass : Class<ShaderNode>) {
+		var node : Node = { x : x, y : y, comment: "", id : id, type: std.Type.getClassName(nameClass) };
+		id++;
+
+		node.instance = std.Type.createInstance(nameClass, []);
+		node.instance.createOutputs();
+
+		this.nodes.set(node.id, node);
+
+		return node.instance;
+	}
+
+	public function removeNode(idNode : Int) {
+		this.nodes.remove(idNode);
+	}
+
+	public function addEdge(edge : Edge) {
+		this.nodes.get(edge.idInput).instance.setInput(edge.nameInput, new NodeVar(this.nodes.get(edge.idOutput).instance, edge.nameOutput));
+		this.nodes.get(edge.idInput).instance.createOutputs();
+	}
+
+	public function removeEdge(idNode, nameInput) {
+		this.nodes.get(idNode).instance.setInput(nameInput, null);
+	}
+
+	public function setPosition(idNode : Int, x : Float, y : Float) {
+		var node = this.nodes.get(idNode);
+		node.x = x;
+		node.y = y;
+	}
+
+	public function getNodes() {
+		return this.nodes;
+	}
+
+	function buildNodeVar(nodeVar : NodeVar) : Array<TExpr>{
+		var node = nodeVar.node;
+		if (node == null)
+			return [];
+		var res = [];
+		var inputs = node.getInputs();
+		for (k in inputs) {
+			res = res.concat(buildNodeVar(k));
+		}
+		var build = nodeVar.getExpr();
+		res = res.concat(build);
+		return res;
+	}
+
+	static function alreadyAddVariable(array : Array<TVar>, variable : TVar) {
+		for (v in array) {
+			if (v.name == variable.name && v.type == variable.type) {
+				return true;
+			}
+		}
+		return false;
+	}
+
+	public function buildFragment() : ShaderData {
+
+		allVariables = [];
+		var content = [];
+
+		for (n in nodes) {
+			n.instance.outputCompiled = [];
+		}
+
+		for (n in nodes) {
+			if (Std.is(n.instance, ShaderInput)) {
+				var variable = Std.instance(n.instance, ShaderInput).variable;
+				if ((variable.kind == Param || variable.kind == Global || variable.kind == Input) && !alreadyAddVariable(allVariables, variable)) {
+					allVariables.push(variable);
+				}
+			}
+			if (Std.is(n.instance, ShaderOutput)) {
+				var variable = Std.instance(n.instance, ShaderOutput).variable;
+				if ( !alreadyAddVariable(allVariables, variable) ) {
+					allVariables.push(variable);
+				}
+				var nodeVar = new NodeVar(n.instance, "input");
+				content = content.concat(buildNodeVar(nodeVar));
+			}
+		}
+
+		return {
+			funs : [{
+					ret : TVoid, kind : Fragment,
+					ref : {
+						name : "fragment",
+						id : 0,
+						kind : Function,
+						type : TFun([{ ret : TVoid, args : [] }])
+					},
+					expr : {
+						p : null,
+						t : TVoid,
+						e : TBlock(content)
+					},
+					args : []
+				}],
+			name: "MON_FRAGMENT",
+			vars: allVariables
+		};
+	}
+
+	public function save() {
+		var edgesJson : Array<Edge> = [];
+		for (n in nodes) {
+			for (k in n.instance.getInputsKey()) {
+				var output =  n.instance.getInput(k);
+				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, parameters : n.instance.saveParameters() }
+			],
+			edges: edgesJson
+		});
+
+		sys.io.File.saveContent(this.filepath, json);
+	}
+}

+ 107 - 0
hrt/shgraph/ShaderInput.hx

@@ -0,0 +1,107 @@
+package hrt.shgraph;
+
+import hide.Element;
+import hxsl.*;
+
+using hxsl.Ast;
+
+@name("Inputs")
+@description("Parameters inputs, it's dynamic")
+@group("Input")
+@noheader()
+class ShaderInput extends ShaderNode {
+
+	@output() var output = SType.Variant;
+
+	@param("Variable") public var variable : TVar;
+
+	override public function getOutput(key : String) : TVar {
+		return variable;
+	}
+
+	override public function build(key : String) : TExpr {
+		return null;
+	}
+
+	static var availableInputs = [{
+						parent: null,
+						id: 0,
+						kind: Global,
+						name: "global.time",
+						type: TFloat
+					},
+					{
+						parent: null,
+						id: 0,
+						kind: Input,
+						name: "input.uv",
+						type: TVec(2, VFloat)
+					}];
+
+	override public function loadParameters(params : Dynamic) {
+		var paramVariable : String = Reflect.field(params, "variable");
+
+		for (c in ShaderNode.availableVariables) {
+			if (c.name == paramVariable) {
+				this.variable = c;
+				return;
+			}
+		}
+		for (c in ShaderInput.availableInputs) {
+			if (c.name == paramVariable) {
+				this.variable = c;
+				return;
+			}
+		}
+	}
+
+	override public function saveParameters() : Dynamic {
+		var parameters = {
+			variable: variable.name
+		};
+
+		return parameters;
+	}
+
+	#if editor
+	override public function getParametersHTML(width : Float) : Array<Element> {
+		var elements = super.getParametersHTML(width);
+		var element = new Element('<div style="width: 110px; height: 30px"></div>');
+		element.append(new Element('<select id="variable"></select>'));
+
+		if (this.variable == null) {
+			this.variable = ShaderNode.availableVariables[0];
+		}
+		var input = element.children("select");
+		var indexOption = 0;
+		for (c in ShaderNode.availableVariables) {
+			input.append(new Element('<option value="${indexOption}">${c.name}</option>'));
+			if (this.variable.name == c.name) {
+				input.val(indexOption);
+			}
+			indexOption++;
+		}
+		for (c in ShaderInput.availableInputs) {
+			input.append(new Element('<option value="${indexOption}">${c.name}</option>'));
+			if (this.variable.name == c.name) {
+				input.val(indexOption);
+			}
+			indexOption++;
+		}
+		input.on("change", function(e) {
+			var value = input.val();
+			if (value < ShaderNode.availableVariables.length) {
+				this.variable = ShaderNode.availableVariables[value];
+			} else {
+				this.variable = ShaderInput.availableInputs[value-ShaderNode.availableVariables.length];
+			}
+		});
+
+
+		elements.push(element);
+
+		return elements;
+	}
+	#end
+
+}

+ 151 - 0
hrt/shgraph/ShaderNode.hx

@@ -0,0 +1,151 @@
+package hrt.shgraph;
+
+import hide.Element;
+using hxsl.Ast;
+
+@:autoBuild(hrt.shgraph.ParseFieldsMacro.build())
+@:keepSub
+class ShaderNode {
+
+	static public var current_id : Int = 0; // TODO : check concurrency
+
+	public var id : Int = current_id++;
+
+	static var availableVariables = [{
+						parent: null,
+						id: 0,
+						kind: Global,
+						name: "pixelColor",
+						type: TVec(4, VFloat)
+					}];
+
+	var inputs : Map<String, NodeVar> = [];
+	var outputs : Map<String, TVar> = [];
+	public var outputCompiled : Map<String, Bool> = []; // todo: put with outputs variable
+
+	public function setId(id : Int) {
+		this.id = id;
+		ShaderNode.current_id = id+1;
+	}
+
+	public function setInput(key : String, s : NodeVar) {
+		if (s == null)
+			inputs.remove(key);
+		else
+			inputs.set(key, s);
+	}
+
+	public function getInput(key : String) : NodeVar {
+		return inputs.get(key);
+	}
+
+	public function getInputsKey() {
+		return [for (k in inputs.keys()) k ];
+	}
+
+	public function getInputs() {
+		return [for (k in inputs.keys()) inputs.get(k) ];
+	}
+
+	function addOutput(key : String, t : Type) {
+		outputs.set(key, { parent: null,
+			id: 0,
+			kind: Local,
+			name: "output_" + id + "_" + key,
+			type: t
+		});
+	}
+
+	function removeOutput(key : String) {
+		outputs.remove(key);
+	}
+
+	function addOutputTvar(tVar : TVar) {
+		outputs.set(tVar.name, tVar);
+	}
+
+	public function createOutputs() : Void {}
+
+	public function getOutput(key : String) : TVar {
+		return outputs.get(key);
+	}
+
+	public function getOutputType(key : String) : Type {
+		var output = getOutput(key);
+		if (output == null)
+			return null;
+		return output.type;
+	}
+
+	public function getOutputTExpr(key : String) : TExpr {
+		var o = getOutput(key);
+		return {
+			e: TVar(o),
+			p: null,
+			t: o.type
+		};
+	}
+
+	public function build(key : String) : TExpr {
+		throw "Not implemented";
+	}
+
+	public function checkTypeAndCompatibilyInput(key : String, type : ShaderType.SType) : Bool {
+		var infoKey = getInputInfo(key);
+		if (infoKey != null && !(ShaderType.checkConversion(type, infoKey))) {
+			return false;
+		}
+		return checkValidityInput(key, type);
+	}
+
+	public function checkValidityInput(key : String, type : ShaderType.SType) : Bool {
+		return true;
+	}
+
+	public function getInputInfo(key : String) : ShaderType.SType {
+		return null;
+	}
+
+	public function getOutputInfo(key : String) : ShaderType.SType {
+		return null;
+	}
+
+	public function loadParameters(params : Dynamic) {
+		var fields = Reflect.fields(params);
+		for (f in fields) {
+			Reflect.setField(this, f, Reflect.field(params, f));
+		}
+	}
+
+	public function saveParameters() : Dynamic {
+		var parameters = {};
+
+		var fields = std.Type.getInstanceFields(std.Type.getClass(this));
+		var metas = haxe.rtti.Meta.getFields(std.Type.getClass(this));
+
+		for (f in fields) {
+			var m = Reflect.field(metas, f);
+			if (m == null) {
+				continue;
+			}
+			if (Reflect.hasField(m, "param")) {
+				Reflect.setField(parameters, f, Reflect.getProperty(this, f));
+			}
+		}
+		return parameters;
+	}
+
+	#if editor
+	public function getParametersHTML(width : Float) : Array<Element> {
+		return [];
+	}
+
+	static public var registeredNodes = new Map<String, Class<ShaderNode>>();
+
+	static public function register(key : String, cl : Class<ShaderNode>) : Bool {
+		registeredNodes.set(key, cl);
+		return true;
+	}
+	#end
+
+}

+ 101 - 0
hrt/shgraph/ShaderOutput.hx

@@ -0,0 +1,101 @@
+package hrt.shgraph;
+
+import hide.Element;
+import h3d.Vector;
+import hxsl.*;
+
+using hxsl.Ast;
+
+@name("Outputs")
+@description("Parameters outputs, it's dynamic")
+@group("Output")
+@noheader()
+class ShaderOutput extends ShaderNode {
+
+	@input("input") var input = SType.Variant;
+
+	@param("Variable") public var variable : TVar;
+
+	var components = [X, Y, Z, W];
+
+	override public function checkValidityInput(key : String, type : ShaderType.SType) : Bool {
+		return ShaderType.checkCompatibilities(type, ShaderType.getType(variable.type));
+	}
+
+	override public function build(key : String) : TExpr {
+
+		return {
+				p : null,
+				t : TVoid,
+				e : TBinop(OpAssign, {
+					e: TVar(variable),
+					p: null,
+					t: variable.type
+				}, input.getVar(variable.type))
+			};
+
+	}
+	static var availableOutputs = [];
+
+	override public function loadParameters(params : Dynamic) {
+		var paramVariable : Array<String> = Reflect.field(params, "variable");
+
+		for (c in ShaderNode.availableVariables) {
+			if (c.name == paramVariable[0]) {
+				this.variable = c;
+				return;
+			}
+		}
+		for (c in ShaderOutput.availableOutputs) {
+			if (c.name == paramVariable[0]) {
+				this.variable = c;
+				return;
+			}
+		}
+	}
+
+	override public function saveParameters() : Dynamic {
+		var parameters = {
+			variable: [variable.name, variable.type.getName()]
+		};
+
+		return parameters;
+	}
+
+
+	#if editor
+	override public function getParametersHTML(width : Float) : Array<Element> {
+		var elements = super.getParametersHTML(width);
+		var element = new Element('<div style="width: 110px; height: 30px"></div>');
+		element.append(new Element('<select id="variable"></select>'));
+
+		if (this.variable == null) {
+			this.variable = ShaderNode.availableVariables[0];
+		}
+		var input = element.children("select");
+		var indexOption = 0;
+		for (c in ShaderNode.availableVariables) {
+			input.append(new Element('<option value="${indexOption}">${c.name}</option>'));
+			if (this.variable.name == c.name) {
+				input.val(indexOption);
+			}
+			indexOption++;
+		}
+		for (c in ShaderOutput.availableOutputs) {
+			input.append(new Element('<option value="${indexOption}">${c.name}</option>'));
+			if (this.variable.name == c.name) {
+				input.val(indexOption);
+			}
+			indexOption++;
+		}
+		input.on("change", function(e) {
+			var value = input.val();
+			this.variable = ShaderNode.availableVariables[value];
+		});
+
+		elements.push(element);
+
+		return elements;
+	}
+	#end
+}

+ 66 - 0
hrt/shgraph/ShaderType.hx

@@ -0,0 +1,66 @@
+package hrt.shgraph;
+
+import hxsl.Ast.Type;
+
+enum SType {
+	/** Bool **/
+	Bool;
+	/** Float **/
+	Float;
+	/** Vector of size 2 **/
+	Vec2;
+	/** Vector of size 3 **/
+	Vec3;
+	/** Vector of size 4 **/
+	Vec4;
+	/** Float or Vectors **/
+	Number;
+	/** Any **/
+	Variant;
+}
+
+class ShaderType {
+
+	static public function getType(type : hxsl.Type) : SType {
+		switch (type) {
+			case TVec(2, VFloat):
+				return Vec2;
+			case TVec(3, VFloat):
+				return Vec3;
+			case TVec(4, VFloat):
+				return Vec4;
+			case TBool:
+				return Bool;
+			case TFloat:
+				return Float;
+			default:
+		}
+		return Variant;
+	}
+
+	static public function checkCompatibilities (a : SType, b : SType) : Bool {
+		return (checkConversion(a, b) || checkConversion(b, a));
+	}
+
+	static public function checkConversion(from : SType, to : SType) {
+		switch (to) {
+			case Vec2:
+				return (from == Float || from == Vec2);
+			case Vec3:
+				return (from == Float || from == Vec2 || from == Vec3);
+			case Vec4:
+				return (from == Float || from == Vec2 || from == Vec3 || from == Vec4);
+			case Bool:
+				return (from == Bool);
+			case Float:
+				return (from == Float);
+			case Number:
+				return (from == Float || from == Vec2 || from == Vec3 || from == Vec4);
+			case Variant:
+				return true;
+			default:
+				return false;
+		}
+	}
+
+}

+ 25 - 0
hrt/shgraph/nodes/Abs.hx

@@ -0,0 +1,25 @@
+package hrt.shgraph.nodes;
+
+import hxsl.*;
+
+using hxsl.Ast;
+
+@name("Abs")
+@description("The output is the result of |A|")
+@group("Math")
+class Abs extends ShaderFunction {
+
+	@input("A") var a = SType.Number;
+
+	public function new() {
+		super(Abs);
+	}
+
+	override public function createOutputs() {
+		if (a != null)
+			addOutput("output", a.getType());
+		else
+			removeOutput("output");
+	}
+
+}

+ 16 - 0
hrt/shgraph/nodes/Add.hx

@@ -0,0 +1,16 @@
+package hrt.shgraph.nodes;
+
+import hxsl.*;
+
+using hxsl.Ast;
+
+@name("Add")
+@description("The output is the result of A + B")
+@group("Operation")
+class Add extends Operation {
+
+	public function new() {
+		super(OpAdd);
+	}
+
+}

+ 48 - 0
hrt/shgraph/nodes/BoolConst.hx

@@ -0,0 +1,48 @@
+package hrt.shgraph.nodes;
+
+import hide.Element;
+using hxsl.Ast;
+
+@name("Bool")
+@description("Boolean input, it's static")
+@group("Input")
+@width(75)
+class BoolConst extends ShaderConst {
+
+	@output() var fakeOutput = SType.Bool;
+
+	@param() var value : Bool = true;
+
+	override public function getOutputTExpr(key : String) : TExpr {
+		return {
+					e: TConst(CBool(value)),
+					p: null,
+					t: TBool
+				};
+	}
+
+	override public function build(key : String) : TExpr {
+		return null;
+	}
+
+	#if editor
+	override public function getParametersHTML(width : Float) : Array<Element> {
+		var elements = super.getParametersHTML(width);
+		var element = new Element('<div style="width: 15px; height: 30px"></div>');
+		element.append(new Element('<input type="checkbox" id="value" ></select>'));
+
+		var input = element.children("input");
+		input.on("change", function(e) {
+			value = (input.is(":checked")) ? true : false;
+		});
+		if (this.value) {
+			input.prop("checked", true);
+		}
+
+		elements.push(element);
+
+		return elements;
+	}
+	#end
+
+}

+ 94 - 0
hrt/shgraph/nodes/Color.hx

@@ -0,0 +1,94 @@
+package hrt.shgraph.nodes;
+
+import hide.Element;
+using hxsl.Ast;
+
+@name("Color")
+@description("Color input, it's static")
+@group("Input")
+@width(100)
+class Color extends ShaderNode {
+
+	@output() var output = SType.Vec4;
+
+	@param() var r : Float = 0;
+	@param() var g : Float = 0;
+	@param() var b : Float = 0;
+	@param() var a : Float = 1;
+
+	override public function createOutputs() {
+		addOutput("output", TVec(4, VFloat));
+	}
+
+	override public function build(key : String) : TExpr {
+
+		return { e: TBinop(OpAssign, {
+						e: TVar(output),
+						p: null,
+						t: output.type
+					}, {
+						e: TCall({
+							e: TGlobal(Vec4),
+							p: null,
+							t: TFun([
+								{
+									ret: output.type,
+									args: [
+									{ name: "r", type : TFloat },
+									{ name: "g", type : TFloat },
+									{ name: "b", type : TFloat },
+									{ name: "a", type : TFloat }]
+								}
+							])
+						}, [{
+								e: TConst(CFloat(r)),
+								p: null,
+								t: TFloat
+							},
+							{
+								e: TConst(CFloat(g)),
+								p: null,
+								t: TFloat
+							},
+							{
+								e: TConst(CFloat(b)),
+								p: null,
+								t: TFloat
+							},{
+								e: TConst(CFloat(a)),
+								p: null,
+								t: TFloat
+							}]),
+						p: null,
+						t: output.type
+					}),
+					p: null,
+					t: output.type
+				};
+	}
+
+	#if editor
+	override public function getParametersHTML(width : Float) : Array<Element> {
+		var elements = super.getParametersHTML(width);
+		var element = new Element('<div style="width: 47px; height: 35px"></div>');
+		var picker = new hide.comp.ColorPicker(true, element);
+
+
+		var start = h3d.Vector.fromArray([r, g, b, a]);
+		picker.value = start.toColor();
+
+		picker.onChange = function(move) {
+			var vec = h3d.Vector.fromColor(picker.value);
+			r = vec.x;
+			g = vec.y;
+			b = vec.z;
+			a = vec.w;
+		};
+
+		elements.push(element);
+
+		return elements;
+	}
+	#end
+
+}

+ 81 - 0
hrt/shgraph/nodes/Combine.hx

@@ -0,0 +1,81 @@
+package hrt.shgraph.nodes;
+
+import hxsl.Types.Vec;
+import hxsl.*;
+
+using hxsl.Ast;
+
+@name("Combine")
+@description("Create a vector of size 4 from 4 floats")
+@group("Channel")
+class Combine extends ShaderNode {
+
+	@input("R") var r = SType.Float;
+	@input("G") var g = SType.Float;
+	@input("B") var b = SType.Float;
+	@input("A") var a = SType.Float;
+
+	@output() var output = SType.Vec4;
+
+	var components = [X, Y, Z, W];
+	var componentsString = ["r", "g", "b", "a"];
+
+	function generateOutputComp(idx : Int) : TExpr {
+		var comp = components[idx];
+
+		var input = getInput(componentsString[idx]);
+		return {
+					p : null,
+					t : output.type,
+					e : TBinop(OpAssign, {
+						e: TSwiz({
+							e: TVar(output),
+							p: null,
+							t : output.type
+						}, [comp]),
+						p: null,
+						t: TVec(1, VFloat)
+					}, input.getVar())
+				};
+	}
+
+	override public function createOutputs() {
+		addOutput("output", TVec(4, VFloat));
+	}
+
+	override public function build(key : String) : TExpr {
+
+		return {
+			p : null,
+			t : output.type,
+			e : TBinop(OpAssign, {
+				e: TVar(output),
+				p: null,
+				t : output.type
+			},
+			{
+				e: TCall({
+					e: TGlobal(Vec4),
+					p: null,
+					t: TFun([
+						{
+							ret: output.type,
+							args: [
+							{ name: "r", type : TFloat },
+							{ name: "g", type : TFloat },
+							{ name: "b", type : TFloat },
+							{ name: "a", type : TFloat }]
+						}
+					])
+				}, [(r != null) ? r.getVar() : { e: TConst(CFloat(0.0)), p: null, t: TFloat },
+					(g != null) ? g.getVar() : { e: TConst(CFloat(0.0)), p: null, t: TFloat },
+					(b != null) ? b.getVar() : { e: TConst(CFloat(0.0)), p: null, t: TFloat },
+					(a != null) ? a.getVar() : { e: TConst(CFloat(1.0)), p: null, t: TFloat }]
+				),
+				p: null,
+				t: output.type
+			})
+		};
+	}
+
+}

+ 85 - 0
hrt/shgraph/nodes/Cond.hx

@@ -0,0 +1,85 @@
+package hrt.shgraph.nodes;
+
+import hide.Element;
+using hxsl.Ast;
+
+@name("Condition")
+@description("Create a custom condition between two inputs")
+@group("Condition")
+class Cond extends ShaderNode {
+
+	@input("left") var leftVar = SType.Variant;
+	@input("right") var rightVar = SType.Variant;
+
+	@output("boolean") var output = SType.Bool;
+
+	@param() var condition : Binop;
+
+	override public function checkValidityInput(key : String, type : ShaderType.SType) : Bool {
+
+		if (key == "leftVar" && rightVar != null)
+			return ShaderType.checkCompatibilities(type, ShaderType.getType(rightVar.getType()));
+
+		if (key == "rightVar" && leftVar != null)
+			return ShaderType.checkCompatibilities(type, ShaderType.getType(leftVar.getType()));
+
+		return true;
+	}
+
+	override public function createOutputs() {
+		addOutput("output", TBool);
+	}
+
+	override public function build(key : String) : TExpr {
+
+		return {
+				p : null,
+				t : output.type,
+				e : TBinop(OpAssign, {
+						e: TVar(output),
+						p: null,
+						t: output.type
+					}, {e: TBinop(this.condition,
+							leftVar.getVar(),
+							rightVar.getVar()),
+						p: null, t: output.type })
+			};
+	}
+
+	var availableConditions = [OpEq, OpNotEq, OpGt, OpGte, OpLt, OpLte, OpAnd, OpOr];
+	var conditionStrings 	= ["==", "!=",    ">",  ">=",  "<",  "<=",  "AND", "OR"];
+
+	override public function saveParameters() : Dynamic {
+		var parameters = {
+			condition: this.condition.getName()
+		};
+
+		return parameters;
+	}
+
+	#if editor
+	override public function getParametersHTML(width : Float) : Array<Element> {
+		var elements = super.getParametersHTML(width);
+		var element = new Element('<div style="width: ${width * 0.8}px; height: 40px"></div>');
+		element.append('<span>Condition</span>');
+		element.append(new Element('<select id="condition"></select>'));
+
+		var input = element.children("select");
+		var indexOption = 0;
+		for (c in conditionStrings) {
+			input.append(new Element('<option value="${indexOption}">${c}</option>'));
+			indexOption++;
+		}
+		input.on("change", function(e) {
+			var value = input.val();
+			this.condition = availableConditions[value];
+		});
+		this.condition = availableConditions[0];
+
+		elements.push(element);
+
+		return elements;
+	}
+	#end
+
+}

+ 16 - 0
hrt/shgraph/nodes/Divide.hx

@@ -0,0 +1,16 @@
+package hrt.shgraph.nodes;
+
+import hxsl.*;
+
+using hxsl.Ast;
+
+@name("Divide")
+@description("The output is the result of A / B")
+@group("Operation")
+class Divide extends Operation {
+
+	public function new() {
+		super(OpDiv);
+	}
+
+}

+ 49 - 0
hrt/shgraph/nodes/FloatConst.hx

@@ -0,0 +1,49 @@
+package hrt.shgraph.nodes;
+
+import hide.Element;
+using hxsl.Ast;
+
+@name("Float")
+@description("Float input, it's static")
+@group("Input")
+@width(100)
+class FloatConst extends ShaderConst {
+
+	@output() var output = SType.Float;
+
+	@param() var value : Float = 0.5;
+
+	override public function getOutputTExpr(key : String) : TExpr {
+		return {
+					e: TConst(CFloat(value)),
+					p: null,
+					t: TFloat
+				};
+	}
+
+	override public function build(key : String) : TExpr {
+		return null;
+	}
+
+	#if editor
+	override public function getParametersHTML(width : Float) : Array<Element> {
+		var elements = super.getParametersHTML(width);
+		var element = new Element('<div style="width: 75px; height: 30px"></div>');
+		element.append(new Element('<input type="text" id="value" style="width: ${width*0.65}px" value="${value}" />'));
+
+		var input = element.children("input");
+		input.on("change", function(e) {
+			try {
+				value = Std.parseFloat(input.val());
+			} catch (e : Dynamic) {
+
+			}
+		});
+
+		elements.push(element);
+
+		return elements;
+	}
+	#end
+
+}

+ 56 - 0
hrt/shgraph/nodes/IfCondition.hx

@@ -0,0 +1,56 @@
+package hrt.shgraph.nodes;
+
+using hxsl.Ast;
+
+@name("If")
+@description("Return the correct input according to the condition")
+@group("Condition")
+class IfCondition extends ShaderNode {
+
+	@input("condition") var condition = SType.Bool;
+	@input("true") var trueVar = SType.Variant;
+	@input("false") var falseVar = SType.Variant;
+
+	@output() var output = SType.Variant;
+
+	override public function checkValidityInput(key : String, type : ShaderType.SType) : Bool {
+
+		if (key == "trueVar" && falseVar != null)
+			return ShaderType.checkCompatibilities(type, ShaderType.getType(falseVar.getType()));
+
+		if (key == "falseVar" && trueVar != null)
+			return ShaderType.checkCompatibilities(type, ShaderType.getType(trueVar.getType()));
+
+		return true;
+	}
+
+	override public function createOutputs() {
+		if (trueVar != null && falseVar != null)
+			addOutput("output", trueVar.getVar(falseVar.getType()).t);
+		else if (trueVar != null)
+			addOutput("output", trueVar.getType());
+		else if (falseVar != null)
+			addOutput("output", falseVar.getType());
+		else
+			removeOutput("output");
+	}
+
+	override public function build(key : String) : TExpr {
+		return {
+			p : null,
+			t: output.type,
+			e : TBinop(OpAssign, {
+					e: TVar(output),
+					p: null,
+					t: output.type
+				}, {
+				e: TIf( condition.getVar(),
+						trueVar.getVar(falseVar.getType()),
+						falseVar.getVar(trueVar.getType())),
+				p: null,
+				t: output.type
+			})
+		};
+	}
+
+}

+ 29 - 0
hrt/shgraph/nodes/Maximum.hx

@@ -0,0 +1,29 @@
+package hrt.shgraph.nodes;
+
+import hxsl.*;
+
+using hxsl.Ast;
+
+@name("Maximum")
+@description("The output is the maximum between A and B")
+@group("Math")
+class Maximum extends ShaderFunction {
+
+	@input("A") var a = SType.Number;
+	@input("B") var b = SType.Number;
+
+	public function new() {
+		super(Max);
+	}
+
+	override public function createOutputs() {
+		if (a != null)
+			addOutput("output", a.getType());
+		else if (b != null)
+			addOutput("output", b.getType());
+		else
+			removeOutput("output");
+	}
+
+
+}

+ 28 - 0
hrt/shgraph/nodes/Minimum.hx

@@ -0,0 +1,28 @@
+package hrt.shgraph.nodes;
+
+import hxsl.*;
+
+using hxsl.Ast;
+
+@name("Minimum")
+@description("The output is the minimum between A and B")
+@group("Math")
+class Minimum extends ShaderFunction {
+
+	@input("A") var a = SType.Number;
+	@input("B") var b = SType.Number;
+
+	public function new() {
+		super(Min);
+	}
+
+	override public function createOutputs() {
+		if (a != null)
+			addOutput("output", a.getType());
+		else if (b != null)
+			addOutput("output", b.getType());
+		else
+			removeOutput("output");
+	}
+
+}

+ 16 - 0
hrt/shgraph/nodes/Multiply.hx

@@ -0,0 +1,16 @@
+package hrt.shgraph.nodes;
+
+import hxsl.*;
+
+using hxsl.Ast;
+
+@name("Multiply")
+@description("The output is the result of A * B")
+@group("Operation")
+class Multiply extends Operation {
+
+	public function new() {
+		super(OpMult);
+	}
+
+}

+ 25 - 0
hrt/shgraph/nodes/Sin.hx

@@ -0,0 +1,25 @@
+package hrt.shgraph.nodes;
+
+import hxsl.*;
+
+using hxsl.Ast;
+
+@name("Sinus")
+@description("The output is the sinus of A")
+@group("Math")
+class Sin extends ShaderFunction {
+
+	@input("A") var a = SType.Number;
+
+	public function new() {
+		super(Sin);
+	}
+
+	override public function createOutputs() {
+		if (a != null)
+			addOutput("output", a.getType());
+		else
+			removeOutput("output");
+	}
+
+}

+ 41 - 0
hrt/shgraph/nodes/Split.hx

@@ -0,0 +1,41 @@
+package hrt.shgraph.nodes;
+
+import hxsl.*;
+
+using hxsl.Ast;
+
+@name("Split")
+@description("Split all components of a vector into floats")
+@group("Channel")
+class Split extends ShaderNode {
+
+	@input("rgba") var input = SType.Vec4;
+
+	@output("r") var r = SType.Float;
+	@output("g") var g = SType.Float;
+	@output("b") var b = SType.Float;
+	@output("a") var a = SType.Float;
+
+	var components = [X, Y, Z, W];
+	var componentsString = ["r", "g", "b", "a"];
+
+	override public function createOutputs() {
+		addOutput("r", TFloat);
+		addOutput("g", TFloat);
+		addOutput("b", TFloat);
+		addOutput("a", TFloat);
+	}
+
+	override public function build(key : String) : TExpr {
+		var compIdx = componentsString.indexOf(key);
+		return { e: TBinop(OpAssign, {
+					e: TVar(getOutput(key)),
+					p: null,
+					t: getOutput(key).type
+				}, {e: TSwiz(input.getVar(), [components[compIdx]]), p: null, t: getOutput(key).type }),
+				p: null,
+				t: getOutput(key).type
+			};
+	}
+
+}

+ 16 - 0
hrt/shgraph/nodes/Subtract.hx

@@ -0,0 +1,16 @@
+package hrt.shgraph.nodes;
+
+import hxsl.*;
+
+using hxsl.Ast;
+
+@name("Subtract")
+@description("The output is the result of A - B")
+@group("Operation")
+class Subtract extends Operation {
+
+	public function new() {
+		super(OpSub);
+	}
+
+}