From b6c28399f89c8f92b016f121e5d8bd1ece91f7fc Mon Sep 17 00:00:00 2001 From: tamat Date: Wed, 15 Jul 2020 21:22:32 +0200 Subject: [PATCH] shader nodes --- build/litegraph.js | 405 ++++++-- css/litegraph-editor.css | 4 +- css/litegraph.css | 9 +- {demo => editor}/demodata/audio.wav | Bin {demo => editor}/demodata/impulse.wav | Bin {demo => editor}/demodata/video.webm | Bin {demo => editor}/examples/audio.json | 0 {demo => editor}/examples/audio_delay.json | 0 {demo => editor}/examples/audio_reverb.json | 0 {demo => editor}/examples/benchmark.json | 0 {demo => editor}/examples/features.json | 0 .../examples/midi_generation.json | 0 {demo => editor}/examples/subgraph.json | 0 {demo => editor}/imgs/grid.png | Bin {demo => editor}/imgs/icon-edit.png | Bin {demo => editor}/imgs/icon-gear.png | Bin {demo => editor}/imgs/icon-load.png | Bin {demo => editor}/imgs/icon-maximize.png | Bin {demo => editor}/imgs/icon-play.png | Bin {demo => editor}/imgs/icon-playstep.png | Bin {demo => editor}/imgs/icon-record.png | Bin {demo => editor}/imgs/icon-save.png | Bin {demo => editor}/imgs/icon-stop.png | Bin {demo => editor}/imgs/load-progress-empty.png | Bin {demo => editor}/imgs/load-progress-full.png | Bin {demo => editor}/imgs/load-progress-grey.png | Bin .../imgs/play-icons-light-alpha.png | Bin {demo => editor}/imgs/play-icons-light.png | Bin {demo => editor}/imgs/play-icons.png | Bin {demo => editor}/index.html | 0 {demo => editor}/js/code.js | 2 - {demo => editor}/js/demos.js | 0 {demo => editor}/js/libs/audiosynth.js | 0 {demo => editor}/js/libs/gl-matrix-min.js | 0 {demo => editor}/js/libs/litegl.js | 0 {demo => editor}/js/libs/midi-parser.js | 0 {demo => editor}/style.css | 0 src/litegraph.js | 325 +++++-- src/nodes/base.js | 37 + src/nodes/gltextures.js | 14 + src/nodes/shaders.js | 911 ++++++++++++++++-- utils/deploy_files.txt | 1 + 42 files changed, 1513 insertions(+), 195 deletions(-) rename {demo => editor}/demodata/audio.wav (100%) rename {demo => editor}/demodata/impulse.wav (100%) rename {demo => editor}/demodata/video.webm (100%) rename {demo => editor}/examples/audio.json (100%) rename {demo => editor}/examples/audio_delay.json (100%) rename {demo => editor}/examples/audio_reverb.json (100%) rename {demo => editor}/examples/benchmark.json (100%) rename {demo => editor}/examples/features.json (100%) rename {demo => editor}/examples/midi_generation.json (100%) rename {demo => editor}/examples/subgraph.json (100%) rename {demo => editor}/imgs/grid.png (100%) rename {demo => editor}/imgs/icon-edit.png (100%) rename {demo => editor}/imgs/icon-gear.png (100%) rename {demo => editor}/imgs/icon-load.png (100%) rename {demo => editor}/imgs/icon-maximize.png (100%) rename {demo => editor}/imgs/icon-play.png (100%) rename {demo => editor}/imgs/icon-playstep.png (100%) rename {demo => editor}/imgs/icon-record.png (100%) rename {demo => editor}/imgs/icon-save.png (100%) rename {demo => editor}/imgs/icon-stop.png (100%) rename {demo => editor}/imgs/load-progress-empty.png (100%) rename {demo => editor}/imgs/load-progress-full.png (100%) rename {demo => editor}/imgs/load-progress-grey.png (100%) rename {demo => editor}/imgs/play-icons-light-alpha.png (100%) rename {demo => editor}/imgs/play-icons-light.png (100%) rename {demo => editor}/imgs/play-icons.png (100%) rename {demo => editor}/index.html (100%) rename {demo => editor}/js/code.js (99%) rename {demo => editor}/js/demos.js (100%) rename {demo => editor}/js/libs/audiosynth.js (100%) rename {demo => editor}/js/libs/gl-matrix-min.js (100%) rename {demo => editor}/js/libs/litegl.js (100%) rename {demo => editor}/js/libs/midi-parser.js (100%) rename {demo => editor}/style.css (100%) diff --git a/build/litegraph.js b/build/litegraph.js index 4c125bf70..30b576438 100644 --- a/build/litegraph.js +++ b/build/litegraph.js @@ -374,7 +374,7 @@ var r = []; for (var i in this.registered_node_types) { var type = this.registered_node_types[i]; - if (filter && type.filter && type.filter != filter) { + if (type.filter != filter) { continue; } @@ -393,6 +393,7 @@ /** * Returns a list with all the node type categories * @method getNodeTypesCategories + * @param {String} filter only nodes with ctor.filter equal can be shown * @return {Array} array with all the names of the categories */ getNodeTypesCategories: function( filter ) { @@ -401,7 +402,7 @@ var type = this.registered_node_types[i]; if ( type.category && !type.skip_list ) { - if(filter && type.filter != filter) + if(type.filter != filter) continue; categories[type.category] = 1; } @@ -619,6 +620,10 @@ /** * LGraph is the class that contain a full graph. We instantiate one and add nodes to it, and then we can run the execution loop. + * supported callbacks: + + onNodeAdded: when a new node is added to the graph + + onNodeRemoved: when a node inside this graph is removed + + onNodeConnectionChange: some connection has changed in the graph (connected or disconnected) * * @class LGraph * @constructor @@ -677,8 +682,8 @@ //nodes this._nodes = []; this._nodes_by_id = {}; - this._nodes_in_order = []; //nodes that are executable sorted in execution order - this._nodes_executable = null; //nodes that contain onExecute + this._nodes_in_order = []; //nodes sorted in execution order + this._nodes_executable = null; //nodes that contain onExecute sorted in execution order //other scene stuff this._groups = []; @@ -692,6 +697,7 @@ //custom data this.config = {}; this.vars = {}; + this.extra = {}; //to store custom data //timing this.globaltime = 0; @@ -729,6 +735,7 @@ } graphcanvas.graph = this; + if (!this.list_of_graphcanvas) { this.list_of_graphcanvas = []; } @@ -1955,9 +1962,13 @@ links: links, groups: groups_info, config: this.config, + extra: this.extra, version: LiteGraph.VERSION }; + if(this.onSerialize) + this.onSerialize(data); + return data; }; @@ -2050,6 +2061,12 @@ } this.updateExecutionOrder(); + + this.extra = data.extra || {}; + + if(this.onConfigure) + this.onConfigure(data); + this._version++; this.setDirtyCanvas(true, true); return error; @@ -2677,6 +2694,23 @@ return null; }; + /** + * Returns the link info in the connection of an input slot + * @method getInputLink + * @param {number} slot + * @return {LLink} object or null + */ + LGraphNode.prototype.getInputLink = function(slot) { + if (!this.inputs) { + return null; + } + if (slot < this.inputs.length) { + var slot_info = this.inputs[slot]; + return this.graph.links[ slot_info.link ]; + } + return null; + }; + /** * returns the node connected in the input slot * @method getInputNode @@ -3137,7 +3171,7 @@ */ LGraphNode.prototype.removeInput = function(slot) { this.disconnectInput(slot); - this.inputs.splice(slot, 1); + var slot_info = this.inputs.splice(slot, 1); for (var i = slot; i < this.inputs.length; ++i) { if (!this.inputs[i]) { continue; @@ -3150,7 +3184,7 @@ } this.setSize( this.computeSize() ); if (this.onInputRemoved) { - this.onInputRemoved(slot); + this.onInputRemoved(slot, slot_info[0] ); } this.setDirtyCanvas(true, true); }; @@ -4041,12 +4075,14 @@ if (!this.console) { this.console = []; } + this.console.push(msg); if (this.console.length > LGraphNode.MAX_CONSOLE) { this.console.shift(); } - this.graph.onNodeTrace(this, msg); + if(this.graph.onNodeTrace) + this.graph.onNodeTrace(this, msg); }; /* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */ @@ -4556,6 +4592,7 @@ LGraphNode.prototype.executeAction = function(action) this.filter = null; //allows to filter to only accept some type of nodes in a graph + this.set_canvas_dirty_on_mouse_event = true; //forces to redraw the canvas if the mouse does anything this.always_render_background = false; this.render_shadows = true; this.render_canvas_border = true; @@ -4570,7 +4607,9 @@ LGraphNode.prototype.executeAction = function(action) this.links_render_mode = LiteGraph.SPLINE_LINK; - this.canvas_mouse = [0, 0]; //mouse in canvas graph coordinates, where 0,0 is the top-left corner of the blue rectangle + this.mouse = [0, 0]; //mouse in canvas coordinates, where 0,0 is the top-left corner of the blue rectangle + this.graph_mouse = [0, 0]; //mouse in graph coordinates, where 0,0 is the top-left corner of the blue rectangle + this.canvas_mouse = this.graph_mouse; //LEGACY: REMOVE THIS, USE GRAPH_MOUSE INSTEAD //to personalize the search box this.onSearchBox = null; @@ -4645,6 +4684,8 @@ LGraphNode.prototype.executeAction = function(action) this.connecting_node = null; this.highlighted_links = {}; + this.dragging_canvas = false; + this.dirty_canvas = true; this.dirty_bgcanvas = true; this.dirty_area = null; @@ -4690,6 +4731,19 @@ LGraphNode.prototype.executeAction = function(action) this.setDirty(true, true); }; + /** + * returns the top level graph (in case there are subgraphs open on the canvas) + * + * @method getTopGraph + * @return {LGraph} graph + */ + LGraphCanvas.prototype.getTopGraph = function() + { + if(this._graph_stack.length) + return this._graph_stack[0]; + return this.graph; + } + /** * opens a graph contained inside a node in the current graph * @@ -5044,8 +5098,19 @@ LGraphNode.prototype.executeAction = function(action) /* LiteGraphCanvas input */ + //used to block future mouse events (because of im gui) + LGraphCanvas.prototype.blockClick = function() + { + this.block_click = true; + this.last_mouseclick = 0; + } + LGraphCanvas.prototype.processMouseDown = function(e) { - if (!this.graph) { + + if( this.set_canvas_dirty_on_mouse_event ) + this.dirty_canvas = true; + + if (!this.graph) { return; } @@ -5079,9 +5144,12 @@ LGraphNode.prototype.executeAction = function(action) var skip_action = false; var now = LiteGraph.getTime(); var is_double_click = now - this.last_mouseclick < 300; + this.mouse[0] = e.localX; + this.mouse[1] = e.localY; + this.graph_mouse[0] = e.canvasX; + this.graph_mouse[1] = e.canvasY; + this.last_click_position = [this.mouse[0],this.mouse[1]]; - this.canvas_mouse[0] = e.canvasX; - this.canvas_mouse[1] = e.canvasY; this.canvas.focus(); LiteGraph.closeAllContextMenus(ref_window); @@ -5253,7 +5321,7 @@ LGraphNode.prototype.executeAction = function(action) var pos = [e.canvasX - node.pos[0], e.canvasY - node.pos[1]]; //widgets - var widget = this.processNodeWidgets( node, this.canvas_mouse, e ); + var widget = this.processNodeWidgets( node, this.graph_mouse, e ); if (widget) { block_drag_node = true; this.node_widget = [node, widget]; @@ -5400,6 +5468,9 @@ LGraphNode.prototype.executeAction = function(action) this.resize(); } + if( this.set_canvas_dirty_on_mouse_event ) + this.dirty_canvas = true; + if (!this.graph) { return; } @@ -5407,30 +5478,42 @@ LGraphNode.prototype.executeAction = function(action) LGraphCanvas.active_canvas = this; this.adjustMouseEvent(e); var mouse = [e.localX, e.localY]; + this.mouse[0] = mouse[0]; + this.mouse[1] = mouse[1]; var delta = [ mouse[0] - this.last_mouse[0], mouse[1] - this.last_mouse[1] ]; this.last_mouse = mouse; - this.canvas_mouse[0] = e.canvasX; - this.canvas_mouse[1] = e.canvasY; + this.graph_mouse[0] = e.canvasX; + this.graph_mouse[1] = e.canvasY; + + if(this.block_click) + { + e.preventDefault(); + return false; + } + e.dragging = this.last_mouse_dragging; if (this.node_widget) { this.processNodeWidgets( this.node_widget[0], - this.canvas_mouse, + this.graph_mouse, e, this.node_widget[1] ); this.dirty_canvas = true; } - if (this.dragging_rectangle) { + if (this.dragging_rectangle) + { this.dragging_rectangle[2] = e.canvasX - this.dragging_rectangle[0]; this.dragging_rectangle[3] = e.canvasY - this.dragging_rectangle[1]; this.dirty_canvas = true; - } else if (this.selected_group && !this.read_only) { + } + else if (this.selected_group && !this.read_only) + { //moving/resizing a group if (this.selected_group_resizing) { this.selected_group.size = [ @@ -5457,18 +5540,11 @@ LGraphNode.prototype.executeAction = function(action) } //get node over - var node = this.graph.getNodeOnPos( - e.canvasX, - e.canvasY, - this.visible_nodes - ); + var node = this.graph.getNodeOnPos(e.canvasX,e.canvasY,this.visible_nodes); //remove mouseover flag for (var i = 0, l = this.graph._nodes.length; i < l; ++i) { - if ( - this.graph._nodes[i].mouseOver && - node != this.graph._nodes[i] - ) { + if (this.graph._nodes[i].mouseOver && node != this.graph._nodes[i] ) { //mouse leave this.graph._nodes[i].mouseOver = false; if (this.node_over && this.node_over.onMouseLeave) { @@ -5499,11 +5575,7 @@ LGraphNode.prototype.executeAction = function(action) //in case the node wants to do something if (node.onMouseMove) { - node.onMouseMove( - e, - [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], - this - ); + node.onMouseMove( e, [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], this ); } //if dragging a link @@ -5515,20 +5587,10 @@ LGraphNode.prototype.executeAction = function(action) //mouse on top of the corner box, don't know what to do } else { //check if I have a slot below de mouse - var slot = this.isOverNodeInput( - node, - e.canvasX, - e.canvasY, - pos - ); + var slot = this.isOverNodeInput( node, e.canvasX, e.canvasY, pos ); if (slot != -1 && node.inputs[slot]) { var slot_type = node.inputs[slot].type; - if ( - LiteGraph.isValidConnection( - this.connecting_output.type, - slot_type - ) - ) { + if ( LiteGraph.isValidConnection( this.connecting_output.type, slot_type ) ) { this._highlight_input = pos; } } else { @@ -5554,7 +5616,7 @@ LGraphNode.prototype.executeAction = function(action) this.canvas.style.cursor = "crosshair"; } } - } else { //outside + } else { //not over a node //search for link connector var over_link = null; @@ -5582,13 +5644,16 @@ LGraphNode.prototype.executeAction = function(action) if (this.canvas) { this.canvas.style.cursor = ""; } - } + } //end + //send event to node if capturing input (used with widgets that allow drag outside of the area of the node) if ( this.node_capturing_input && this.node_capturing_input != node && this.node_capturing_input.onMouseMove ) { this.node_capturing_input.onMouseMove(e,[e.canvasX - this.node_capturing_input.pos[0],e.canvasY - this.node_capturing_input.pos[1]], this); } + //node being dragged if (this.node_dragged && !this.live_mode) { + //console.log("draggin!",this.selected_nodes); for (var i in this.selected_nodes) { var n = this.selected_nodes[i]; n.pos[0] += delta[0] / this.ds.scale; @@ -5622,9 +5687,12 @@ LGraphNode.prototype.executeAction = function(action) * @method processMouseUp **/ LGraphCanvas.prototype.processMouseUp = function(e) { - if (!this.graph) { + + if( this.set_canvas_dirty_on_mouse_event ) + this.dirty_canvas = true; + + if (!this.graph) return; - } var window = this.getCanvasWindow(); var document = window.document; @@ -5639,12 +5707,19 @@ LGraphNode.prototype.executeAction = function(action) var now = LiteGraph.getTime(); e.click_time = now - this.last_mouseclick; this.last_mouse_dragging = false; + this.last_click_position = null; + + if(this.block_click) + { + console.log("foo"); + this.block_click = false; //used to avoid sending twice a click in a immediate button + } if (e.which == 1) { if( this.node_widget ) { - this.processNodeWidgets( this.node_widget[0], this.canvas_mouse, e ); + this.processNodeWidgets( this.node_widget[0], this.graph_mouse, e ); } //left button @@ -6263,10 +6338,8 @@ LGraphNode.prototype.executeAction = function(action) * selects several nodes (or adds them to the current selection) * @method selectNodes **/ - LGraphCanvas.prototype.selectNodes = function( - nodes, - add_to_current_selection - ) { + LGraphCanvas.prototype.selectNodes = function( nodes, add_to_current_selection ) + { if (!add_to_current_selection) { this.deselectAllNodes(); } @@ -6562,7 +6635,7 @@ LGraphNode.prototype.executeAction = function(action) * @method draw **/ LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas) { - if (!this.canvas) { + if (!this.canvas || this.canvas.width == 0 || this.canvas.height == 0) { return; } @@ -6643,7 +6716,7 @@ LGraphNode.prototype.executeAction = function(action) if (this.bgcanvas == this.canvas) { this.drawBackCanvas(); } else { - ctx.drawImage(this.bgcanvas, 0, 0); + ctx.drawImage( this.bgcanvas, 0, 0 ); } //rendering @@ -6712,7 +6785,7 @@ LGraphNode.prototype.executeAction = function(action) this.renderLink( ctx, this.connecting_pos, - [this.canvas_mouse[0], this.canvas_mouse[1]], + [this.graph_mouse[0], this.graph_mouse[1]], null, false, null, @@ -6786,6 +6859,12 @@ LGraphNode.prototype.executeAction = function(action) ctx.restore(); } + //draws panel in the corner + if (this._graph_stack && this._graph_stack.length) { + this.drawSubgraphPanel( ctx ); + } + + if (this.onDrawOverlay) { this.onDrawOverlay(ctx); } @@ -6801,13 +6880,151 @@ LGraphNode.prototype.executeAction = function(action) } }; + /** + * draws the panel in the corner that shows subgraph properties + * @method drawSubgraphPanel + **/ + LGraphCanvas.prototype.drawSubgraphPanel = function(ctx) { + var subgraph = this.graph; + var subnode = subgraph._subgraph_node; + if(!subnode) + { + console.warn("subgraph without subnode"); + return; + } + + var num = subnode.inputs ? subnode.inputs.length : 0; + var w = 300; + var h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6); + + ctx.fillStyle = "#111"; + ctx.globalAlpha = 0.8; + ctx.beginPath(); + ctx.roundRect(10,10,w, (num + 1) * h + 50,8 ); + ctx.fill(); + ctx.globalAlpha = 1; + + ctx.fillStyle = "#888"; + ctx.font = "14px Arial"; + ctx.textAlign = "left"; + ctx.fillText( "Graph Inputs", 20, 34 ); + var pos = this.mouse; + + if( this.drawButton( w - 20, 20,20,20, "X", "#151515" ) ) + { + this.closeSubgraph(); + return; + } + + var y = 50; + ctx.font = "20px Arial"; + if(subnode.inputs) + for(var i = 0; i < subnode.inputs.length; ++i) + { + var input = subnode.inputs[i]; + if(input.not_subgraph_input) + continue; + + //input button clicked + if( this.drawButton( 20,y+2,w - 20, h - 2 ) ) + { + var type = subnode.constructor.input_node_type || "graph/input"; + var newnode = LiteGraph.createNode( type ); + if(newnode) + { + subgraph.add( newnode ); + this.block_click = false; + this.last_click_position = null; + this.selectNodes([newnode]); + this.node_dragged = newnode; + this.dragging_canvas = false; + newnode.setProperty("name",input.name); + newnode.setProperty("type",input.type); + this.node_dragged.pos[0] = this.graph_mouse[0] - 5; + this.node_dragged.pos[1] = this.graph_mouse[1] - 5; + } + else + console.error("graph input node not found:",type); + } + + ctx.fillStyle = "#9C9"; + ctx.beginPath(); + ctx.arc(w - 16,y + h * 0.5,5,0,2*Math.PI); + ctx.fill(); + + ctx.fillStyle = "#AAA"; + ctx.fillText( input.name, 50, y + h*0.75 ); + var tw = ctx.measureText( input.name ); + ctx.fillStyle = "#777"; + ctx.fillText( input.type, 50 + tw.width + 10, y + h*0.75 ); + + y += h; + } + + //add + button + if( this.drawButton( 20,y+2,w - 20, h - 2, "+", "#151515", "#222" ) ) + { + this.showSubgraphPropertiesDialog( subnode ); + } + } + + //Draws a button into the canvas overlay and computes if it was clicked using the immediate gui paradigm + LGraphCanvas.prototype.drawButton = function( x,y,w,h, text, bgcolor, hovercolor, textcolor ) + { + var ctx = this.ctx; + bgcolor = bgcolor || LiteGraph.NODE_DEFAULT_COLOR; + hovercolor = hovercolor || "#555"; + textcolor = textcolor || LiteGraph.NODE_TEXT_COLOR; + + var pos = this.mouse; + var hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); + pos = this.last_click_position; + var clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); + + ctx.fillStyle = hover ? hovercolor : bgcolor; + if(clicked) + ctx.fillStyle = "#AAA"; + ctx.beginPath(); + ctx.roundRect(x,y,w,h,4 ); + ctx.fill(); + + if(text != null) + { + if(text.constructor == String) + { + ctx.fillStyle = textcolor; + ctx.textAlign = "center"; + ctx.font = ((h * 0.65)|0) + "px Arial"; + ctx.fillText( text, x + w * 0.5,y + h * 0.75 ); + ctx.textAlign = "left"; + } + } + + var was_clicked = clicked && !this.block_click; + if(clicked) + this.blockClick(); + return was_clicked; + } + + LGraphCanvas.prototype.isAreaClicked = function( x,y,w,h, hold_click ) + { + var pos = this.mouse; + var hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); + pos = this.last_click_position; + var clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); + var was_clicked = clicked && !this.block_click; + if(clicked && hold_click) + this.blockClick(); + return was_clicked; + } + /** * draws some useful stats in the corner of the canvas * @method renderInfo **/ LGraphCanvas.prototype.renderInfo = function(ctx, x, y) { - x = x || 0; - y = y || 0; + x = x || 10; + y = y || this.canvas.height - 80; ctx.save(); ctx.translate(x, y); @@ -7520,7 +7737,7 @@ LGraphNode.prototype.executeAction = function(action) ctx.shadowColor = "transparent"; if (node.onDrawBackground) { - node.onDrawBackground(ctx, this, this.canvas, this.canvas_mouse ); + node.onDrawBackground(ctx, this, this.canvas, this.graph_mouse ); } //title bg (remember, it is rendered ABOVE the node) @@ -7670,9 +7887,10 @@ LGraphNode.prototype.executeAction = function(action) //subgraph box if (!node.flags.collapsed && node.subgraph && !node.skip_subgraph_button) { - ctx.fillStyle = "#555"; var w = LiteGraph.NODE_TITLE_HEIGHT; var x = node.size[0] - w; + var over = LiteGraph.isInsideRectangle( this.graph_mouse[0] - node.pos[0], this.graph_mouse[1] - node.pos[1], x+2, -w+2, w-4, w-4 ); + ctx.fillStyle = over ? "#888" : "#555"; if( shape == LiteGraph.BOX_SHAPE || low_quality) ctx.fillRect(x+2, -w+2, w-4, w-4); else @@ -8558,6 +8776,7 @@ LGraphNode.prototype.executeAction = function(action) } } else if (delta) { //clicked in arrow, used for combos var index = -1; + this.last_mouseclick = 0; //avoids dobl click event if(values.constructor === Object) index = values_list.indexOf( String( w.value ) ) + delta; else @@ -8836,8 +9055,11 @@ LGraphNode.prototype.executeAction = function(action) LGraphCanvas.onMenuAdd = function(node, options, e, prev_menu, callback) { var canvas = LGraphCanvas.active_canvas; var ref_window = canvas.getCanvasWindow(); + var graph = canvas.graph; + if(!graph) + return; - var values = LiteGraph.getNodeTypesCategories( canvas.filter ); + var values = LiteGraph.getNodeTypesCategories( canvas.filter || graph.filter ); var entries = []; for (var i in values) { if (values[i]) { @@ -8850,7 +9072,7 @@ LGraphNode.prototype.executeAction = function(action) function inner_clicked(v, option, e) { var category = v.value; - var node_types = LiteGraph.getNodeTypesInCategory( category, canvas.filter ); + var node_types = LiteGraph.getNodeTypesInCategory( category, canvas.filter || graph.filter ); var values = []; for (var i in node_types) { if (!node_types[i].skip_list) { @@ -10085,14 +10307,16 @@ LGraphNode.prototype.executeAction = function(action) for(var i = 0; i < node.inputs.length; ++i) { var input = node.inputs[i]; - var html = "NameType"; + if(input.not_subgraph_input) + continue; + var html = " "; var elem = panel.addHTML(html,"subgraph_property"); elem.dataset["name"] = input.name; elem.dataset["slot"] = i; - elem.querySelector(".name").value = input.name; - elem.querySelector(".type").value = input.type; + elem.querySelector(".name").innerText = input.name; + elem.querySelector(".type").innerText = input.type; elem.querySelector("button").addEventListener("click",function(e){ - node.removeInput( this.parentNode.dataset["slot"] ); + node.removeInput( Number( this.parentNode.dataset["slot"] ) ); inner_refresh(); }); } @@ -11583,6 +11807,43 @@ if (typeof exports != "undefined") { } }; + Subgraph.prototype.onDrawBackground = function(ctx, graphcanvas, canvas, pos) + { + if(this.flags.collapsed) + return; + + var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5; + + //button + var over = LiteGraph.isInsideRectangle(pos[0],pos[1],this.pos[0],this.pos[1] + y,this.size[0],LiteGraph.NODE_TITLE_HEIGHT); + ctx.fillStyle = over ? "#555" : "#222"; + ctx.beginPath(); + ctx.roundRect( 0, y, this.size[0]+1, LiteGraph.NODE_TITLE_HEIGHT, 0, 8); + ctx.fill(); + + //button + ctx.textAlign = "center"; + ctx.font = "24px Arial"; + ctx.fillStyle = over ? "#DDD" : "#999"; + ctx.fillText( "+", this.size[0] * 0.5, y + 24 ); + } + + Subgraph.prototype.onMouseDown = function(e, localpos, graphcanvas) + { + var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5; + if(localpos[1] > y) + { + graphcanvas.showSubgraphPropertiesDialog(this); + } + } + + Subgraph.prototype.computeSize = function() + { + var num_inputs = this.inputs ? this.inputs.length : 0; + var num_outputs = this.outputs ? this.outputs.length : 0; + return [ 200, Math.max(num_inputs,num_outputs) * LiteGraph.NODE_SLOT_HEIGHT + LiteGraph.NODE_TITLE_HEIGHT ]; + } + //**** INPUTS *********************************** Subgraph.prototype.onSubgraphTrigger = function(event, param) { var slot = this.findOutputSlot(event); @@ -18469,6 +18730,20 @@ void main() {\n\ LGraphTextureToViewport.desc = "Texture to viewport"; LGraphTextureToViewport._prev_viewport = new Float32Array(4); + + LGraphTextureToViewport.prototype.onDrawBackground = function( ctx ) + { + if ( this.flags.collapsed || this.size[1] <= 40 ) + return; + + var tex = this.getInputData(0); + if (!tex) { + return; + } + + ctx.drawImage( ctx == gl ? tex : gl.canvas, 10,30, this.size[0] -20, this.size[1] -40); + } + LGraphTextureToViewport.prototype.onExecute = function() { var tex = this.getInputData(0); if (!tex) { diff --git a/css/litegraph-editor.css b/css/litegraph-editor.css index d286949b6..4b1239602 100755 --- a/css/litegraph-editor.css +++ b/css/litegraph-editor.css @@ -181,7 +181,7 @@ display: inline-block; width: 90px; height: 15px; - background-image: url("../demo/imgs/load-progress-empty.png"); + background-image: url("../editor/imgs/load-progress-empty.png"); } .litegraph-editor .cpuload .fgload, @@ -190,7 +190,7 @@ width: 4px; height: 15px; max-width: 90px; - background-image: url("../demo/imgs/load-progress-full.png"); + background-image: url("../editor/imgs/load-progress-full.png"); } .litegraph-editor textarea.code, .litegraph-editor div.code { diff --git a/css/litegraph.css b/css/litegraph.css index 6f03567a4..80a203ad4 100755 --- a/css/litegraph.css +++ b/css/litegraph.css @@ -261,12 +261,11 @@ .litegraph .dialog .dialog-content { height: calc(100% - 90px); - width: calc(100% - 10px); + width: 100%; min-height: 100px; - /*background-color: black;*/ - padding: 4px; display: inline-block; color: #AAA; + /*background-color: black;*/ } .litegraph .dialog .dialog-content h3 { @@ -308,7 +307,7 @@ .litegraph .dialog .property { margin-bottom: 2px; - padding: 0; + padding: 4px; } .litegraph .dialog .property_name { @@ -362,7 +361,7 @@ } .litegraph .subgraph_property { - padding-bottom: 4px; + padding: 4px; } .litegraph .subgraph_property:hover { diff --git a/demo/demodata/audio.wav b/editor/demodata/audio.wav similarity index 100% rename from demo/demodata/audio.wav rename to editor/demodata/audio.wav diff --git a/demo/demodata/impulse.wav b/editor/demodata/impulse.wav similarity index 100% rename from demo/demodata/impulse.wav rename to editor/demodata/impulse.wav diff --git a/demo/demodata/video.webm b/editor/demodata/video.webm similarity index 100% rename from demo/demodata/video.webm rename to editor/demodata/video.webm diff --git a/demo/examples/audio.json b/editor/examples/audio.json similarity index 100% rename from demo/examples/audio.json rename to editor/examples/audio.json diff --git a/demo/examples/audio_delay.json b/editor/examples/audio_delay.json similarity index 100% rename from demo/examples/audio_delay.json rename to editor/examples/audio_delay.json diff --git a/demo/examples/audio_reverb.json b/editor/examples/audio_reverb.json similarity index 100% rename from demo/examples/audio_reverb.json rename to editor/examples/audio_reverb.json diff --git a/demo/examples/benchmark.json b/editor/examples/benchmark.json similarity index 100% rename from demo/examples/benchmark.json rename to editor/examples/benchmark.json diff --git a/demo/examples/features.json b/editor/examples/features.json similarity index 100% rename from demo/examples/features.json rename to editor/examples/features.json diff --git a/demo/examples/midi_generation.json b/editor/examples/midi_generation.json similarity index 100% rename from demo/examples/midi_generation.json rename to editor/examples/midi_generation.json diff --git a/demo/examples/subgraph.json b/editor/examples/subgraph.json similarity index 100% rename from demo/examples/subgraph.json rename to editor/examples/subgraph.json diff --git a/demo/imgs/grid.png b/editor/imgs/grid.png similarity index 100% rename from demo/imgs/grid.png rename to editor/imgs/grid.png diff --git a/demo/imgs/icon-edit.png b/editor/imgs/icon-edit.png similarity index 100% rename from demo/imgs/icon-edit.png rename to editor/imgs/icon-edit.png diff --git a/demo/imgs/icon-gear.png b/editor/imgs/icon-gear.png similarity index 100% rename from demo/imgs/icon-gear.png rename to editor/imgs/icon-gear.png diff --git a/demo/imgs/icon-load.png b/editor/imgs/icon-load.png similarity index 100% rename from demo/imgs/icon-load.png rename to editor/imgs/icon-load.png diff --git a/demo/imgs/icon-maximize.png b/editor/imgs/icon-maximize.png similarity index 100% rename from demo/imgs/icon-maximize.png rename to editor/imgs/icon-maximize.png diff --git a/demo/imgs/icon-play.png b/editor/imgs/icon-play.png similarity index 100% rename from demo/imgs/icon-play.png rename to editor/imgs/icon-play.png diff --git a/demo/imgs/icon-playstep.png b/editor/imgs/icon-playstep.png similarity index 100% rename from demo/imgs/icon-playstep.png rename to editor/imgs/icon-playstep.png diff --git a/demo/imgs/icon-record.png b/editor/imgs/icon-record.png similarity index 100% rename from demo/imgs/icon-record.png rename to editor/imgs/icon-record.png diff --git a/demo/imgs/icon-save.png b/editor/imgs/icon-save.png similarity index 100% rename from demo/imgs/icon-save.png rename to editor/imgs/icon-save.png diff --git a/demo/imgs/icon-stop.png b/editor/imgs/icon-stop.png similarity index 100% rename from demo/imgs/icon-stop.png rename to editor/imgs/icon-stop.png diff --git a/demo/imgs/load-progress-empty.png b/editor/imgs/load-progress-empty.png similarity index 100% rename from demo/imgs/load-progress-empty.png rename to editor/imgs/load-progress-empty.png diff --git a/demo/imgs/load-progress-full.png b/editor/imgs/load-progress-full.png similarity index 100% rename from demo/imgs/load-progress-full.png rename to editor/imgs/load-progress-full.png diff --git a/demo/imgs/load-progress-grey.png b/editor/imgs/load-progress-grey.png similarity index 100% rename from demo/imgs/load-progress-grey.png rename to editor/imgs/load-progress-grey.png diff --git a/demo/imgs/play-icons-light-alpha.png b/editor/imgs/play-icons-light-alpha.png similarity index 100% rename from demo/imgs/play-icons-light-alpha.png rename to editor/imgs/play-icons-light-alpha.png diff --git a/demo/imgs/play-icons-light.png b/editor/imgs/play-icons-light.png similarity index 100% rename from demo/imgs/play-icons-light.png rename to editor/imgs/play-icons-light.png diff --git a/demo/imgs/play-icons.png b/editor/imgs/play-icons.png similarity index 100% rename from demo/imgs/play-icons.png rename to editor/imgs/play-icons.png diff --git a/demo/index.html b/editor/index.html similarity index 100% rename from demo/index.html rename to editor/index.html diff --git a/demo/js/code.js b/editor/js/code.js similarity index 99% rename from demo/js/code.js rename to editor/js/code.js index 4f6ecb712..4ff061cca 100644 --- a/demo/js/code.js +++ b/editor/js/code.js @@ -162,7 +162,5 @@ function enableWebGL() gl.clear( gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT ); gl.viewport(0,0,gl.canvas.width, gl.canvas.height ); } - - } } diff --git a/demo/js/demos.js b/editor/js/demos.js similarity index 100% rename from demo/js/demos.js rename to editor/js/demos.js diff --git a/demo/js/libs/audiosynth.js b/editor/js/libs/audiosynth.js similarity index 100% rename from demo/js/libs/audiosynth.js rename to editor/js/libs/audiosynth.js diff --git a/demo/js/libs/gl-matrix-min.js b/editor/js/libs/gl-matrix-min.js similarity index 100% rename from demo/js/libs/gl-matrix-min.js rename to editor/js/libs/gl-matrix-min.js diff --git a/demo/js/libs/litegl.js b/editor/js/libs/litegl.js similarity index 100% rename from demo/js/libs/litegl.js rename to editor/js/libs/litegl.js diff --git a/demo/js/libs/midi-parser.js b/editor/js/libs/midi-parser.js similarity index 100% rename from demo/js/libs/midi-parser.js rename to editor/js/libs/midi-parser.js diff --git a/demo/style.css b/editor/style.css similarity index 100% rename from demo/style.css rename to editor/style.css diff --git a/src/litegraph.js b/src/litegraph.js index 6ca05a999..eaea5ab5f 100755 --- a/src/litegraph.js +++ b/src/litegraph.js @@ -372,7 +372,7 @@ var r = []; for (var i in this.registered_node_types) { var type = this.registered_node_types[i]; - if (filter && type.filter && type.filter != filter) { + if (type.filter != filter) { continue; } @@ -391,6 +391,7 @@ /** * Returns a list with all the node type categories * @method getNodeTypesCategories + * @param {String} filter only nodes with ctor.filter equal can be shown * @return {Array} array with all the names of the categories */ getNodeTypesCategories: function( filter ) { @@ -399,7 +400,7 @@ var type = this.registered_node_types[i]; if ( type.category && !type.skip_list ) { - if(filter && type.filter != filter) + if(type.filter != filter) continue; categories[type.category] = 1; } @@ -617,6 +618,10 @@ /** * LGraph is the class that contain a full graph. We instantiate one and add nodes to it, and then we can run the execution loop. + * supported callbacks: + + onNodeAdded: when a new node is added to the graph + + onNodeRemoved: when a node inside this graph is removed + + onNodeConnectionChange: some connection has changed in the graph (connected or disconnected) * * @class LGraph * @constructor @@ -675,8 +680,8 @@ //nodes this._nodes = []; this._nodes_by_id = {}; - this._nodes_in_order = []; //nodes that are executable sorted in execution order - this._nodes_executable = null; //nodes that contain onExecute + this._nodes_in_order = []; //nodes sorted in execution order + this._nodes_executable = null; //nodes that contain onExecute sorted in execution order //other scene stuff this._groups = []; @@ -690,6 +695,7 @@ //custom data this.config = {}; this.vars = {}; + this.extra = {}; //to store custom data //timing this.globaltime = 0; @@ -727,6 +733,7 @@ } graphcanvas.graph = this; + if (!this.list_of_graphcanvas) { this.list_of_graphcanvas = []; } @@ -1953,9 +1960,13 @@ links: links, groups: groups_info, config: this.config, + extra: this.extra, version: LiteGraph.VERSION }; + if(this.onSerialize) + this.onSerialize(data); + return data; }; @@ -2048,6 +2059,12 @@ } this.updateExecutionOrder(); + + this.extra = data.extra || {}; + + if(this.onConfigure) + this.onConfigure(data); + this._version++; this.setDirtyCanvas(true, true); return error; @@ -4056,12 +4073,14 @@ if (!this.console) { this.console = []; } + this.console.push(msg); if (this.console.length > LGraphNode.MAX_CONSOLE) { this.console.shift(); } - this.graph.onNodeTrace(this, msg); + if(this.graph.onNodeTrace) + this.graph.onNodeTrace(this, msg); }; /* Forces to redraw or the main canvas (LGraphNode) or the bg canvas (links) */ @@ -4571,6 +4590,7 @@ LGraphNode.prototype.executeAction = function(action) this.filter = null; //allows to filter to only accept some type of nodes in a graph + this.set_canvas_dirty_on_mouse_event = true; //forces to redraw the canvas if the mouse does anything this.always_render_background = false; this.render_shadows = true; this.render_canvas_border = true; @@ -4585,7 +4605,9 @@ LGraphNode.prototype.executeAction = function(action) this.links_render_mode = LiteGraph.SPLINE_LINK; - this.canvas_mouse = [0, 0]; //mouse in canvas graph coordinates, where 0,0 is the top-left corner of the blue rectangle + this.mouse = [0, 0]; //mouse in canvas coordinates, where 0,0 is the top-left corner of the blue rectangle + this.graph_mouse = [0, 0]; //mouse in graph coordinates, where 0,0 is the top-left corner of the blue rectangle + this.canvas_mouse = this.graph_mouse; //LEGACY: REMOVE THIS, USE GRAPH_MOUSE INSTEAD //to personalize the search box this.onSearchBox = null; @@ -4660,6 +4682,8 @@ LGraphNode.prototype.executeAction = function(action) this.connecting_node = null; this.highlighted_links = {}; + this.dragging_canvas = false; + this.dirty_canvas = true; this.dirty_bgcanvas = true; this.dirty_area = null; @@ -4705,6 +4729,19 @@ LGraphNode.prototype.executeAction = function(action) this.setDirty(true, true); }; + /** + * returns the top level graph (in case there are subgraphs open on the canvas) + * + * @method getTopGraph + * @return {LGraph} graph + */ + LGraphCanvas.prototype.getTopGraph = function() + { + if(this._graph_stack.length) + return this._graph_stack[0]; + return this.graph; + } + /** * opens a graph contained inside a node in the current graph * @@ -5059,8 +5096,19 @@ LGraphNode.prototype.executeAction = function(action) /* LiteGraphCanvas input */ + //used to block future mouse events (because of im gui) + LGraphCanvas.prototype.blockClick = function() + { + this.block_click = true; + this.last_mouseclick = 0; + } + LGraphCanvas.prototype.processMouseDown = function(e) { - if (!this.graph) { + + if( this.set_canvas_dirty_on_mouse_event ) + this.dirty_canvas = true; + + if (!this.graph) { return; } @@ -5094,9 +5142,12 @@ LGraphNode.prototype.executeAction = function(action) var skip_action = false; var now = LiteGraph.getTime(); var is_double_click = now - this.last_mouseclick < 300; + this.mouse[0] = e.localX; + this.mouse[1] = e.localY; + this.graph_mouse[0] = e.canvasX; + this.graph_mouse[1] = e.canvasY; + this.last_click_position = [this.mouse[0],this.mouse[1]]; - this.canvas_mouse[0] = e.canvasX; - this.canvas_mouse[1] = e.canvasY; this.canvas.focus(); LiteGraph.closeAllContextMenus(ref_window); @@ -5268,7 +5319,7 @@ LGraphNode.prototype.executeAction = function(action) var pos = [e.canvasX - node.pos[0], e.canvasY - node.pos[1]]; //widgets - var widget = this.processNodeWidgets( node, this.canvas_mouse, e ); + var widget = this.processNodeWidgets( node, this.graph_mouse, e ); if (widget) { block_drag_node = true; this.node_widget = [node, widget]; @@ -5415,6 +5466,9 @@ LGraphNode.prototype.executeAction = function(action) this.resize(); } + if( this.set_canvas_dirty_on_mouse_event ) + this.dirty_canvas = true; + if (!this.graph) { return; } @@ -5422,30 +5476,42 @@ LGraphNode.prototype.executeAction = function(action) LGraphCanvas.active_canvas = this; this.adjustMouseEvent(e); var mouse = [e.localX, e.localY]; + this.mouse[0] = mouse[0]; + this.mouse[1] = mouse[1]; var delta = [ mouse[0] - this.last_mouse[0], mouse[1] - this.last_mouse[1] ]; this.last_mouse = mouse; - this.canvas_mouse[0] = e.canvasX; - this.canvas_mouse[1] = e.canvasY; + this.graph_mouse[0] = e.canvasX; + this.graph_mouse[1] = e.canvasY; + + if(this.block_click) + { + e.preventDefault(); + return false; + } + e.dragging = this.last_mouse_dragging; if (this.node_widget) { this.processNodeWidgets( this.node_widget[0], - this.canvas_mouse, + this.graph_mouse, e, this.node_widget[1] ); this.dirty_canvas = true; } - if (this.dragging_rectangle) { + if (this.dragging_rectangle) + { this.dragging_rectangle[2] = e.canvasX - this.dragging_rectangle[0]; this.dragging_rectangle[3] = e.canvasY - this.dragging_rectangle[1]; this.dirty_canvas = true; - } else if (this.selected_group && !this.read_only) { + } + else if (this.selected_group && !this.read_only) + { //moving/resizing a group if (this.selected_group_resizing) { this.selected_group.size = [ @@ -5472,18 +5538,11 @@ LGraphNode.prototype.executeAction = function(action) } //get node over - var node = this.graph.getNodeOnPos( - e.canvasX, - e.canvasY, - this.visible_nodes - ); + var node = this.graph.getNodeOnPos(e.canvasX,e.canvasY,this.visible_nodes); //remove mouseover flag for (var i = 0, l = this.graph._nodes.length; i < l; ++i) { - if ( - this.graph._nodes[i].mouseOver && - node != this.graph._nodes[i] - ) { + if (this.graph._nodes[i].mouseOver && node != this.graph._nodes[i] ) { //mouse leave this.graph._nodes[i].mouseOver = false; if (this.node_over && this.node_over.onMouseLeave) { @@ -5514,11 +5573,7 @@ LGraphNode.prototype.executeAction = function(action) //in case the node wants to do something if (node.onMouseMove) { - node.onMouseMove( - e, - [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], - this - ); + node.onMouseMove( e, [e.canvasX - node.pos[0], e.canvasY - node.pos[1]], this ); } //if dragging a link @@ -5530,20 +5585,10 @@ LGraphNode.prototype.executeAction = function(action) //mouse on top of the corner box, don't know what to do } else { //check if I have a slot below de mouse - var slot = this.isOverNodeInput( - node, - e.canvasX, - e.canvasY, - pos - ); + var slot = this.isOverNodeInput( node, e.canvasX, e.canvasY, pos ); if (slot != -1 && node.inputs[slot]) { var slot_type = node.inputs[slot].type; - if ( - LiteGraph.isValidConnection( - this.connecting_output.type, - slot_type - ) - ) { + if ( LiteGraph.isValidConnection( this.connecting_output.type, slot_type ) ) { this._highlight_input = pos; } } else { @@ -5569,7 +5614,7 @@ LGraphNode.prototype.executeAction = function(action) this.canvas.style.cursor = "crosshair"; } } - } else { //outside + } else { //not over a node //search for link connector var over_link = null; @@ -5597,13 +5642,16 @@ LGraphNode.prototype.executeAction = function(action) if (this.canvas) { this.canvas.style.cursor = ""; } - } + } //end + //send event to node if capturing input (used with widgets that allow drag outside of the area of the node) if ( this.node_capturing_input && this.node_capturing_input != node && this.node_capturing_input.onMouseMove ) { this.node_capturing_input.onMouseMove(e,[e.canvasX - this.node_capturing_input.pos[0],e.canvasY - this.node_capturing_input.pos[1]], this); } + //node being dragged if (this.node_dragged && !this.live_mode) { + //console.log("draggin!",this.selected_nodes); for (var i in this.selected_nodes) { var n = this.selected_nodes[i]; n.pos[0] += delta[0] / this.ds.scale; @@ -5637,9 +5685,12 @@ LGraphNode.prototype.executeAction = function(action) * @method processMouseUp **/ LGraphCanvas.prototype.processMouseUp = function(e) { - if (!this.graph) { + + if( this.set_canvas_dirty_on_mouse_event ) + this.dirty_canvas = true; + + if (!this.graph) return; - } var window = this.getCanvasWindow(); var document = window.document; @@ -5654,12 +5705,19 @@ LGraphNode.prototype.executeAction = function(action) var now = LiteGraph.getTime(); e.click_time = now - this.last_mouseclick; this.last_mouse_dragging = false; + this.last_click_position = null; + + if(this.block_click) + { + console.log("foo"); + this.block_click = false; //used to avoid sending twice a click in a immediate button + } if (e.which == 1) { if( this.node_widget ) { - this.processNodeWidgets( this.node_widget[0], this.canvas_mouse, e ); + this.processNodeWidgets( this.node_widget[0], this.graph_mouse, e ); } //left button @@ -6278,10 +6336,8 @@ LGraphNode.prototype.executeAction = function(action) * selects several nodes (or adds them to the current selection) * @method selectNodes **/ - LGraphCanvas.prototype.selectNodes = function( - nodes, - add_to_current_selection - ) { + LGraphCanvas.prototype.selectNodes = function( nodes, add_to_current_selection ) + { if (!add_to_current_selection) { this.deselectAllNodes(); } @@ -6577,7 +6633,7 @@ LGraphNode.prototype.executeAction = function(action) * @method draw **/ LGraphCanvas.prototype.draw = function(force_canvas, force_bgcanvas) { - if (!this.canvas) { + if (!this.canvas || this.canvas.width == 0 || this.canvas.height == 0) { return; } @@ -6658,7 +6714,7 @@ LGraphNode.prototype.executeAction = function(action) if (this.bgcanvas == this.canvas) { this.drawBackCanvas(); } else { - ctx.drawImage(this.bgcanvas, 0, 0); + ctx.drawImage( this.bgcanvas, 0, 0 ); } //rendering @@ -6727,7 +6783,7 @@ LGraphNode.prototype.executeAction = function(action) this.renderLink( ctx, this.connecting_pos, - [this.canvas_mouse[0], this.canvas_mouse[1]], + [this.graph_mouse[0], this.graph_mouse[1]], null, false, null, @@ -6801,6 +6857,12 @@ LGraphNode.prototype.executeAction = function(action) ctx.restore(); } + //draws panel in the corner + if (this._graph_stack && this._graph_stack.length) { + this.drawSubgraphPanel( ctx ); + } + + if (this.onDrawOverlay) { this.onDrawOverlay(ctx); } @@ -6816,13 +6878,151 @@ LGraphNode.prototype.executeAction = function(action) } }; + /** + * draws the panel in the corner that shows subgraph properties + * @method drawSubgraphPanel + **/ + LGraphCanvas.prototype.drawSubgraphPanel = function(ctx) { + var subgraph = this.graph; + var subnode = subgraph._subgraph_node; + if(!subnode) + { + console.warn("subgraph without subnode"); + return; + } + + var num = subnode.inputs ? subnode.inputs.length : 0; + var w = 300; + var h = Math.floor(LiteGraph.NODE_SLOT_HEIGHT * 1.6); + + ctx.fillStyle = "#111"; + ctx.globalAlpha = 0.8; + ctx.beginPath(); + ctx.roundRect(10,10,w, (num + 1) * h + 50,8 ); + ctx.fill(); + ctx.globalAlpha = 1; + + ctx.fillStyle = "#888"; + ctx.font = "14px Arial"; + ctx.textAlign = "left"; + ctx.fillText( "Graph Inputs", 20, 34 ); + var pos = this.mouse; + + if( this.drawButton( w - 20, 20,20,20, "X", "#151515" ) ) + { + this.closeSubgraph(); + return; + } + + var y = 50; + ctx.font = "20px Arial"; + if(subnode.inputs) + for(var i = 0; i < subnode.inputs.length; ++i) + { + var input = subnode.inputs[i]; + if(input.not_subgraph_input) + continue; + + //input button clicked + if( this.drawButton( 20,y+2,w - 20, h - 2 ) ) + { + var type = subnode.constructor.input_node_type || "graph/input"; + var newnode = LiteGraph.createNode( type ); + if(newnode) + { + subgraph.add( newnode ); + this.block_click = false; + this.last_click_position = null; + this.selectNodes([newnode]); + this.node_dragged = newnode; + this.dragging_canvas = false; + newnode.setProperty("name",input.name); + newnode.setProperty("type",input.type); + this.node_dragged.pos[0] = this.graph_mouse[0] - 5; + this.node_dragged.pos[1] = this.graph_mouse[1] - 5; + } + else + console.error("graph input node not found:",type); + } + + ctx.fillStyle = "#9C9"; + ctx.beginPath(); + ctx.arc(w - 16,y + h * 0.5,5,0,2*Math.PI); + ctx.fill(); + + ctx.fillStyle = "#AAA"; + ctx.fillText( input.name, 50, y + h*0.75 ); + var tw = ctx.measureText( input.name ); + ctx.fillStyle = "#777"; + ctx.fillText( input.type, 50 + tw.width + 10, y + h*0.75 ); + + y += h; + } + + //add + button + if( this.drawButton( 20,y+2,w - 20, h - 2, "+", "#151515", "#222" ) ) + { + this.showSubgraphPropertiesDialog( subnode ); + } + } + + //Draws a button into the canvas overlay and computes if it was clicked using the immediate gui paradigm + LGraphCanvas.prototype.drawButton = function( x,y,w,h, text, bgcolor, hovercolor, textcolor ) + { + var ctx = this.ctx; + bgcolor = bgcolor || LiteGraph.NODE_DEFAULT_COLOR; + hovercolor = hovercolor || "#555"; + textcolor = textcolor || LiteGraph.NODE_TEXT_COLOR; + + var pos = this.mouse; + var hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); + pos = this.last_click_position; + var clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); + + ctx.fillStyle = hover ? hovercolor : bgcolor; + if(clicked) + ctx.fillStyle = "#AAA"; + ctx.beginPath(); + ctx.roundRect(x,y,w,h,4 ); + ctx.fill(); + + if(text != null) + { + if(text.constructor == String) + { + ctx.fillStyle = textcolor; + ctx.textAlign = "center"; + ctx.font = ((h * 0.65)|0) + "px Arial"; + ctx.fillText( text, x + w * 0.5,y + h * 0.75 ); + ctx.textAlign = "left"; + } + } + + var was_clicked = clicked && !this.block_click; + if(clicked) + this.blockClick(); + return was_clicked; + } + + LGraphCanvas.prototype.isAreaClicked = function( x,y,w,h, hold_click ) + { + var pos = this.mouse; + var hover = LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); + pos = this.last_click_position; + var clicked = pos && LiteGraph.isInsideRectangle( pos[0], pos[1], x,y,w,h ); + var was_clicked = clicked && !this.block_click; + if(clicked && hold_click) + this.blockClick(); + return was_clicked; + } + /** * draws some useful stats in the corner of the canvas * @method renderInfo **/ LGraphCanvas.prototype.renderInfo = function(ctx, x, y) { - x = x || 0; - y = y || 0; + x = x || 10; + y = y || this.canvas.height - 80; ctx.save(); ctx.translate(x, y); @@ -7535,7 +7735,7 @@ LGraphNode.prototype.executeAction = function(action) ctx.shadowColor = "transparent"; if (node.onDrawBackground) { - node.onDrawBackground(ctx, this, this.canvas, this.canvas_mouse ); + node.onDrawBackground(ctx, this, this.canvas, this.graph_mouse ); } //title bg (remember, it is rendered ABOVE the node) @@ -7685,9 +7885,10 @@ LGraphNode.prototype.executeAction = function(action) //subgraph box if (!node.flags.collapsed && node.subgraph && !node.skip_subgraph_button) { - ctx.fillStyle = "#555"; var w = LiteGraph.NODE_TITLE_HEIGHT; var x = node.size[0] - w; + var over = LiteGraph.isInsideRectangle( this.graph_mouse[0] - node.pos[0], this.graph_mouse[1] - node.pos[1], x+2, -w+2, w-4, w-4 ); + ctx.fillStyle = over ? "#888" : "#555"; if( shape == LiteGraph.BOX_SHAPE || low_quality) ctx.fillRect(x+2, -w+2, w-4, w-4); else @@ -8573,6 +8774,7 @@ LGraphNode.prototype.executeAction = function(action) } } else if (delta) { //clicked in arrow, used for combos var index = -1; + this.last_mouseclick = 0; //avoids dobl click event if(values.constructor === Object) index = values_list.indexOf( String( w.value ) ) + delta; else @@ -8851,8 +9053,11 @@ LGraphNode.prototype.executeAction = function(action) LGraphCanvas.onMenuAdd = function(node, options, e, prev_menu, callback) { var canvas = LGraphCanvas.active_canvas; var ref_window = canvas.getCanvasWindow(); + var graph = canvas.graph; + if(!graph) + return; - var values = LiteGraph.getNodeTypesCategories( canvas.filter ); + var values = LiteGraph.getNodeTypesCategories( canvas.filter || graph.filter ); var entries = []; for (var i in values) { if (values[i]) { @@ -8865,7 +9070,7 @@ LGraphNode.prototype.executeAction = function(action) function inner_clicked(v, option, e) { var category = v.value; - var node_types = LiteGraph.getNodeTypesInCategory( category, canvas.filter ); + var node_types = LiteGraph.getNodeTypesInCategory( category, canvas.filter || graph.filter ); var values = []; for (var i in node_types) { if (!node_types[i].skip_list) { @@ -10100,6 +10305,8 @@ LGraphNode.prototype.executeAction = function(action) for(var i = 0; i < node.inputs.length; ++i) { var input = node.inputs[i]; + if(input.not_subgraph_input) + continue; var html = " "; var elem = panel.addHTML(html,"subgraph_property"); elem.dataset["name"] = input.name; diff --git a/src/nodes/base.js b/src/nodes/base.js index e0f4050cd..9f82b1636 100755 --- a/src/nodes/base.js +++ b/src/nodes/base.js @@ -131,6 +131,43 @@ } }; + Subgraph.prototype.onDrawBackground = function(ctx, graphcanvas, canvas, pos) + { + if(this.flags.collapsed) + return; + + var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5; + + //button + var over = LiteGraph.isInsideRectangle(pos[0],pos[1],this.pos[0],this.pos[1] + y,this.size[0],LiteGraph.NODE_TITLE_HEIGHT); + ctx.fillStyle = over ? "#555" : "#222"; + ctx.beginPath(); + ctx.roundRect( 0, y, this.size[0]+1, LiteGraph.NODE_TITLE_HEIGHT, 0, 8); + ctx.fill(); + + //button + ctx.textAlign = "center"; + ctx.font = "24px Arial"; + ctx.fillStyle = over ? "#DDD" : "#999"; + ctx.fillText( "+", this.size[0] * 0.5, y + 24 ); + } + + Subgraph.prototype.onMouseDown = function(e, localpos, graphcanvas) + { + var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5; + if(localpos[1] > y) + { + graphcanvas.showSubgraphPropertiesDialog(this); + } + } + + Subgraph.prototype.computeSize = function() + { + var num_inputs = this.inputs ? this.inputs.length : 0; + var num_outputs = this.outputs ? this.outputs.length : 0; + return [ 200, Math.max(num_inputs,num_outputs) * LiteGraph.NODE_SLOT_HEIGHT + LiteGraph.NODE_TITLE_HEIGHT ]; + } + //**** INPUTS *********************************** Subgraph.prototype.onSubgraphTrigger = function(event, param) { var slot = this.findOutputSlot(event); diff --git a/src/nodes/gltextures.js b/src/nodes/gltextures.js index 3c24ddf69..0ea30c449 100755 --- a/src/nodes/gltextures.js +++ b/src/nodes/gltextures.js @@ -1229,6 +1229,20 @@ void main() {\n\ LGraphTextureToViewport.desc = "Texture to viewport"; LGraphTextureToViewport._prev_viewport = new Float32Array(4); + + LGraphTextureToViewport.prototype.onDrawBackground = function( ctx ) + { + if ( this.flags.collapsed || this.size[1] <= 40 ) + return; + + var tex = this.getInputData(0); + if (!tex) { + return; + } + + ctx.drawImage( ctx == gl ? tex : gl.canvas, 10,30, this.size[0] -20, this.size[1] -40); + } + LGraphTextureToViewport.prototype.onExecute = function() { var tex = this.getInputData(0); if (!tex) { diff --git a/src/nodes/shaders.js b/src/nodes/shaders.js index f5b22969c..bf03cfdac 100644 --- a/src/nodes/shaders.js +++ b/src/nodes/shaders.js @@ -1,18 +1,112 @@ (function(global) { - var LiteGraph = global.LiteGraph; - var LGraphCanvas = global.LGraphCanvas; if (typeof GL == "undefined") return; + var LiteGraph = global.LiteGraph; + var LGraphCanvas = global.LGraphCanvas; - //common actions to all shader node classes - function setShaderNode( node_ctor ) + var SHADERNODES_COLOR = "#345"; + + var LGShaders = LiteGraph.Shaders = {}; + + var GLSL_types = LGShaders.GLSL_types = ["float","vec2","vec3","vec4","mat3","mat4","sampler2D","samplerCube"]; + var GLSL_types_const = LGShaders.GLSL_types_const = ["float","vec2","vec3","vec4"]; + + var GLSL_functions_desc = { + "radians": "T radians(T degrees)", + "degrees": "T degrees(T radians)", + "sin": "T sin(T angle)", + "cos": "T cos(T angle)", + "tan": "T tan(T angle)", + "asin": "T asin(T x)", + "acos": "T acos(T x)", + "atan": "T atan(T x)", + "atan2": "T atan(T x,T y)", + "pow": "T pow(T x,T y)", + "exp": "T exp(T x)", + "log": "T log(T x)", + "exp2": "T exp2(T x)", + "log2": "T log2(T x)", + "sqrt": "T sqrt(T x)", + "inversesqrt": "T inversesqrt(T x)", + "abs": "T abs(T x)", + "sign": "T sign(T x)", + "floor": "T floor(T x)", + "ceil": "T ceil(T x)", + "fract": "T fract(T x)", + "mod": "T mod(T x,T y)", //"T mod(T x,float y)" + "min": "T min(T x,T y)", + "max": "T max(T x,T y)", + "clamp": "T clamp(T x,T minVal,T maxVal)", + "mix": "T mix(T x,T y,T a)", //"T mix(T x,T y,float a)" + "step": "T step(T edge, T x)", //"T step(float edge, T x)" + "smoothstep": "T smoothstep(T edge, T x)", //"T smoothstep(float edge, T x)" + "length":"float length(T x)", + "distance":"float distance(T p0, T p1)", + "normalize":"T normalize(T x)", + "dot": "float dot(T x,T y)", + "cross": "vec3 cross(vec3 x,vec3 y)" + }; + + //parse them + var GLSL_functions = {}; + var GLSL_functions_name = []; + parseGLSLDescriptions(); + + function parseGLSLDescriptions() { - node_ctor.color = "#345"; + GLSL_functions_name.length = 0; + + for(var i in GLSL_functions_desc) + { + var op = GLSL_functions_desc[i]; + var index = op.indexOf(" "); + var return_type = op.substr(0,index); + var index2 = op.indexOf("(",index); + var func_name = op.substr(index,index2-index).trim(); + var params = op.substr(index2 + 1, op.length - index2 - 2).split(","); + for(var j in params) + { + var p = params[j].split(" "); + params[j] = { type: p[0], name: p[1] }; + } + GLSL_functions[i] = { return_type: return_type, func: func_name, params: params }; + GLSL_functions_name.push( func_name ); + //console.log( GLSL_functions[i] ); + } } - function getShaderInputLinkName( node, slot ) + //common actions to all shader node classes + function registerShaderNode( type, node_ctor ) + { + //static attributes + node_ctor.color = SHADERNODES_COLOR; + node_ctor.filter = "shader"; + + //common methods + node_ctor.prototype.clearDestination = function(){ this.shader_destination = {}; } + node_ctor.prototype.propagateDestination = function propagateDestination( dest_name ) + { + this.shader_destination[ dest_name ] = true; + if(this.inputs) + for(var i = 0; i < this.inputs.length; ++i) + { + var origin_node = this.getInputNode(i); + if(origin_node) + origin_node.propagateDestination( dest_name ); + } + } + + LiteGraph.registerNodeType( type, node_ctor ); + } + + function getShaderNodeVarName( node, name ) + { + return "VAR_" + (name || "TEMP") + "_" + node.id; + } + + function getInputLinkID( node, slot ) { if(!node.inputs) return null; @@ -28,12 +122,60 @@ return "link_" + origin_node.id + "_" + link.origin_slot; } - function getShaderOutputLinkName( node, slot ) + function getOutputLinkID( node, slot ) { + if (!node.isOutputConnected(0)) + return null; return "link_" + node.id + "_" + slot; } - //used to host a shader body ******************* + LGShaders.registerShaderNode = registerShaderNode; + LGShaders.getInputLinkID = getInputLinkID; + LGShaders.getOutputLinkID = getOutputLinkID; + LGShaders.getShaderNodeVarName = getShaderNodeVarName; + LGShaders.parseGLSLDescriptions = parseGLSLDescriptions; + + var valueToGLSL = LiteGraph.valueToGLSL = function valueToGLSL( v, type ) + { + var n = 5; //num decimals + if(!type) + { + if(v.constructor === Number) + type = "float"; + else if(v.length) + { + switch(v.length) + { + case 2: type = "vec2"; break; + case 3: type = "vec3"; break; + case 4: type = "vec4"; break; + case 9: type = "mat3"; break; + case 16: type = "mat4"; break; + default: + throw("unknown type for glsl value size"); + } + } + else + throw("unknown type for glsl value: " + v.constructor); + } + switch(type) + { + case 'float': return v.toFixed(n); break; + case 'vec2': return "vec2(" + v[0].toFixed(n) + "," + v[1].toFixed(n) + ")"; break; + case 'color3': + case 'vec3': return "vec3(" + v[0].toFixed(n) + "," + v[1].toFixed(n) + "," + v[2].toFixed(n) + ")"; break; + case 'color4': + case 'vec4': return "vec4(" + v[0].toFixed(n) + "," + v[1].toFixed(n) + "," + v[2].toFixed(n) + "," + v[3].toFixed(n) + ")"; break; + case 'mat3': return "mat3(1.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,1.0)"; break; //not fully supported yet + case 'mat4': return "mat4(1.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,0.0,1.0,0.0,0.0,0.0,0.0,1.0)"; break;//not fully supported yet + default: + throw("unknown glsl type in valueToGLSL:", type); + } + + return ""; + } + + //used to host a shader body ************************************** function LGShaderContext() { this.vs_template = ""; @@ -42,8 +184,6 @@ this._codeparts = {}; } - LGShaderContext.valid_types = ["float","vec2","vec3","vec4","sampler2D","mat3","mat4","int","boolean"]; - LGShaderContext.prototype.clear = function() { this._uniforms = {}; @@ -55,16 +195,20 @@ this._uniforms[ name ] = type; } - LGShaderContext.prototype.addCode = function( hook, code ) + LGShaderContext.prototype.addCode = function( hook, code, destinations ) { - if(!this._codeparts[ hook ]) - this._codeparts[ hook ] = code + "\n"; - else - this._codeparts[ hook ] += code + "\n"; + destinations = destinations || {"":""}; + for(var i in destinations) + { + var h = i ? i + "_" + hook : hook; + if(!this._codeparts[ h ]) + this._codeparts[ h ] = code + "\n"; + else + this._codeparts[ h ] += code + "\n"; + } } - //generates the shader code from the template and the - LGShaderContext.prototype.computeShader = function( shader ) + LGShaderContext.prototype.computeShaderCode = function() { var uniforms = ""; for(var i in this._uniforms) @@ -75,23 +219,77 @@ var vs_code = GL.Shader.replaceCodeUsingContext( this.vs_template, parts ); var fs_code = GL.Shader.replaceCodeUsingContext( this.fs_template, parts ); + return { + vs_code: vs_code, + fs_code: fs_code + }; + } + + //generates the shader code from the template and the + LGShaderContext.prototype.computeShader = function( shader ) + { + var finalcode = this.computeShaderCode(); + console.log( finalcode.vs_code, finalcode.fs_code ); try { if(shader) - shader.updateShader( vs_code, fs_code ); + shader.updateShader( finalcode.vs_code, finalcode.fs_code ); else - shader = new GL.Shader( vs_code, fs_code ); + shader = new GL.Shader( finalcode.vs_code, finalcode.fs_code ); + this._shader_error = false; return shader; } catch (err) { + if(!this._shader_error) + { + console.error(err); + if(err.indexOf("Fragment shader") != -1) + console.log( finalcode.fs_code ); + else + console.log( finalcode.vs_code ); + } + this._shader_error = true; return null; } return null;//never here } + //represents a fragment of code exported by a node + /* + function LGShaderCodeBlock( node, code, uniforms ) + { + this.node = node || null; + this.uniforms = uniforms || null; + this.parts = {}; + if(code) + { + if(code.constructor === String) + this.parts.code = code; + else + this.parts = code; + } + } + + LGShaderCodeBlock.prototype.addUniform = function( name, type ) + { + if(!this.uniforms) + this.uniforms = {}; + this.uniforms[ name ] = type; + } + + LGShaderCodeBlock.prototype.addCode = function( hook, code ) + { + if(!this.parts[ hook ]) + this.parts[ hook ] = code + "\n"; + else + this.parts[ hook ] += code + "\n"; + } + */ + + // LGraphShaderGraph ***************************** // applies a shader graph to texture, it can be uses as an example @@ -102,9 +300,10 @@ this.subgraph = new LiteGraph.LGraph(); this.subgraph._subgraph_node = this; this.subgraph._is_subgraph = true; + this.subgraph.filter = "shader"; - var subnode = LiteGraph.createNode("shader/fragcolor"); - this.subgraph.pos = [300,100]; + var subnode = LiteGraph.createNode("output/fragcolor"); + subnode.pos = [300,100]; this.subgraph.add( subnode ); this.size = [180,60]; @@ -118,16 +317,17 @@ } LGraphShaderGraph.template = "\n\ - precision highp float;\n\ - varying vec2 v_coord;\n\ - {{varying}}\n\ - {{uniforms}}\n\ - void main() {\n\ - vec2 uv = v_coord;\n\ - vec4 color = vec4(0.0);\n\ - {{fs_code}}\n\ - gl_FragColor = color;\n\ - }\n\ +precision highp float;\n\ +varying vec2 v_coord;\n\ +{{varying}}\n\ +{{uniforms}}\n\ +{{fs_functions}}\n\ +void main() {\n\n\ +vec2 uv = v_coord;\n\ +vec4 color = vec4(0.0);\n\ +{{fs_code}}\n\ +gl_FragColor = color;\n\ +}\n\ "; LGraphShaderGraph.widgets_info = { @@ -136,6 +336,8 @@ LGraphShaderGraph.title = "ShaderGraph"; LGraphShaderGraph.desc = "Builds a shader using a graph"; + LGraphShaderGraph.input_node_type = "input/uniform"; + LGraphShaderGraph.title_color = SHADERNODES_COLOR; LGraphShaderGraph.prototype.onSerialize = function(o) { @@ -207,17 +409,19 @@ this.setOutputData(0, texture ); }; + //add input node inside subgraph LGraphShaderGraph.prototype.onInputAdded = function( slot_info ) { - var subnode = LiteGraph.createNode("shader/uniform"); - subnode.properties.name = slot_info.name; - subnode.properties.type = slot_info.type; + var subnode = LiteGraph.createNode("input/uniform"); + subnode.setProperty("name",slot_info.name); + subnode.setProperty("type",slot_info.type); this.subgraph.add( subnode ); } + //remove all LGraphShaderGraph.prototype.onInputRemoved = function( slot, slot_info ) { - var nodes = this.subgraph.findNodesByType("shader/uniform"); + var nodes = this.subgraph.findNodesByType("input/uniform"); for(var i = 0; i < nodes.length; ++i) { var node = nodes[i]; @@ -242,8 +446,25 @@ //prepare context this._context.clear(); + //grab output nodes + var vertexout = this.subgraph.findNodesByType("output/vertex"); + vertexout = vertexout && vertexout.length ? vertexout[0] : null; + var fragmentout = this.subgraph.findNodesByType("output/fragcolor"); + fragmentout = fragmentout && fragmentout.length ? fragmentout[0] : null; + + if(!fragmentout) //?? + return null; + + this.subgraph.sendEventToAllNodes( "clearDestination" ); + + //propagate back destinations + if(vertexout) + vertexout.propagateDestination("vs"); + if(fragmentout) + fragmentout.propagateDestination("fs"); + //gets code from graph - this.subgraph.sendEventToAllNodes("onGetCode", this._context); + this.subgraph.sendEventToAllNodes("onGetCode", this._context ); //compile shader var shader = this._context.computeShader(); @@ -265,6 +486,13 @@ if(this.flags.collapsed) return; + //allows to preview the node if the canvas is a webgl canvas + var tex = this.getOutputData(0); + var inputs_y = this.inputs ? this.inputs.length * LiteGraph.NODE_SLOT_HEIGHT : 0; + if (tex && ctx == tex.gl && this.size[1] > inputs_y + LiteGraph.NODE_TITLE_HEIGHT ) { + ctx.drawImage( tex, 10,y, this.size[0] - 20, this.size[1] - inputs_y - LiteGraph.NODE_TITLE_HEIGHT ); + } + var y = this.size[1] - LiteGraph.NODE_TITLE_HEIGHT + 0.5; //button @@ -290,6 +518,17 @@ } } + LGraphShaderGraph.prototype.getExtraMenuOptions = function(graphcanvas) + { + var that = this; + var options = [{ content: "Print Code", callback: function(){ + var code = that._context.computeShaderCode(); + console.log( code.vs_code, code.fs_code ); + }}]; + + return options; + } + LiteGraph.registerNodeType( "texture/shaderGraph", LGraphShaderGraph ); //Shader Nodes *************************** @@ -305,7 +544,14 @@ LGraphShaderUniform.prototype.getTitle = function() { - return "uniform " + this.properties.name; + if( this.properties.name && this.flags.collapsed) + return this.properties.type + " " + this.properties.name; + return "Uniform"; + } + + LGraphShaderUniform.prototype.onPropertyChanged = function(name,value) + { + this.outputs[0].name = this.properties.type + " " + this.properties.name; } LGraphShaderUniform.prototype.onGetCode = function( context ) @@ -317,9 +563,11 @@ type = "float"; else if(type == "texture") type = "sampler2D"; - if ( LGShaderContext.valid_types.indexOf(type) == -1 ) + if ( LGShaders.GLSL_types.indexOf(type) == -1 ) return; + context.addUniform( "u_" + this.properties.name, type ); + this.setOutputData( 0, type ); } LGraphShaderUniform.prototype.getOutputVarName = function(slot) @@ -327,10 +575,7 @@ return "u_" + this.properties.name; } - setShaderNode( LGraphShaderUniform ); - - LiteGraph.registerNodeType( "shader/uniform", LGraphShaderUniform ); - + registerShaderNode( "input/uniform", LGraphShaderUniform ); function LGraphShaderAttribute() { @@ -349,12 +594,17 @@ LGraphShaderAttribute.prototype.onGetCode = function( context ) { var type = this.properties.type; - if( !type || LGShaderContext.valid_types.indexOf(type) == -1 ) + if( !type || LGShaders.GLSL_types.indexOf(type) == -1 ) return; if(type == "number") type = "float"; if( this.properties.name != "coord") - context.addCode( "varying", " varying " + type +" v_" + this.properties.name ); + { + context.addCode( "varying", " varying " + type +" v_" + this.properties.name + ";" ); + //if( !context.varyings[ this.properties.name ] ) + //context.addCode( "vs_code", "v_" + this.properties.name + " = " + input_name + ";" ); + } + this.setOutputData( 0, type ); } LGraphShaderAttribute.prototype.getOutputVarName = function(slot) @@ -362,15 +612,13 @@ return "v_" + this.properties.name; } - setShaderNode( LGraphShaderAttribute ); - - LiteGraph.registerNodeType( "shader/attribute", LGraphShaderAttribute ); - + registerShaderNode( "input/attribute", LGraphShaderAttribute ); function LGraphShaderSampler2D() { this.addInput("tex", "sampler2D"); this.addInput("uv", "vec2"); this.addOutput("rgba", "vec4"); + this.addOutput("rgb", "vec3"); } LGraphShaderSampler2D.title = "Sampler2D"; @@ -378,22 +626,301 @@ LGraphShaderSampler2D.prototype.onGetCode = function( context ) { - var texname = getShaderInputLinkName( this, 0 ); - var code; + var texname = getInputLinkID( this, 0 ); + var varname = getShaderNodeVarName(this); + var code = "vec4 " + varname + " = vec4(0.0);\n"; if(texname) { - var uvname = getShaderInputLinkName( this, 1 ) || "v_coord"; - code = "vec4 " + getShaderOutputLinkName( this, 0 ) + " = texture2D("+texname+","+uvname+");"; + var uvname = getInputLinkID( this, 1 ) || "v_coord"; + code += varname + " = texture2D("+texname+","+uvname+");\n"; } - else - code = "vec4 " + getShaderOutputLinkName( this, 0 ) + " = vec4(0.0);"; - context.addCode( "fs_code", code ); + + var link0 = getOutputLinkID( this, 0 ); + if(link0) + code += "vec4 " + getOutputLinkID( this, 0 ) + " = "+varname+";\n"; + + var link1 = getOutputLinkID( this, 1 ); + if(link1) + code += "vec3 " + getOutputLinkID( this, 1 ) + " = "+varname+".xyz;\n"; + + context.addCode( "code", code, this.shader_destination ); + this.setOutputData( 0, "vec4" ); + this.setOutputData( 1, "vec3" ); } - setShaderNode( LGraphShaderSampler2D ); + registerShaderNode( "texture/sampler2D", LGraphShaderSampler2D ); - LiteGraph.registerNodeType( "shader/sampler2D", LGraphShaderSampler2D ); + //********************************* + function LGraphShaderConstant() + { + this.addOutput("","float"); + + this.properties = { + type: "float", + value: 0 + }; + + this.addWidget("combo","type","float",null, { values: GLSL_types_const, property: "type" } ); + this.updateWidgets(); + } + + LGraphShaderConstant.title = "const"; + + LGraphShaderConstant.prototype.getTitle = function() + { + if(this.flags.collapsed) + return valueToGLSL( this.properties.value, this.properties.type ); + return "Const"; + } + + LGraphShaderConstant.prototype.onPropertyChanged = function(name,value) + { + var that = this; + if(name == "type") + { + if(this.outputs[0].type != value) + { + this.disconnectOutput(0); + this.outputs[0].type = value; + this.widgets.length = 1; //remove extra widgets + this.updateWidgets(); + } + } + } + + LGraphShaderConstant.prototype.updateWidgets = function( old_value ) + { + var that = this; + var old_value = this.properties.value; + var options = { step: 0.01 }; + switch(this.properties.type) + { + case 'float': + this.properties.value = 0; + this.addWidget("number","v",0,{ step:0.01, property: "value" }); + break; + case 'vec2': + this.properties.value = old_value && old_value.length == 2 ? [old_value[0],old_value[1]] : [0,0,0]; + this.addWidget("number","x",0,options, function(v){ that.properties.value[0] = v; }); + this.addWidget("number","y",0,options, function(v){ that.properties.value[1] = v; }); + break; + case 'vec3': + this.properties.value = old_value && old_value.length == 3 ? [old_value[0],old_value[1],old_value[2]] : [0,0,0]; + this.addWidget("number","x",0,options, function(v){ that.properties.value[0] = v; }); + this.addWidget("number","y",0,options, function(v){ that.properties.value[1] = v; }); + this.addWidget("number","z",0,options, function(v){ that.properties.value[2] = v; }); + break; + case 'vec4': + this.properties.value = old_value && old_value.length == 4 ? [old_value[0],old_value[1],old_value[2],old_value[3]] : [0,0,0,0]; + this.addWidget("number","x",0,options, function(v){ that.properties.value[0] = v; }); + this.addWidget("number","y",0,options, function(v){ that.properties.value[1] = v; }); + this.addWidget("number","z",0,options, function(v){ that.properties.value[2] = v; }); + this.addWidget("number","w",0,options, function(v){ that.properties.value[3] = v; }); + break; + default: + console.error("unknown type for constant"); + } + } + + LGraphShaderConstant.prototype.onGetCode = function( context ) + { + var value = valueToGLSL( this.properties.value, this.properties.type ); + var link_name = getOutputLinkID(this,0); + if(!link_name) //not connected + return; + + var code = " " + this.properties.type + " " + link_name + " = " + value + ";"; + context.addCode( "code", code, this.shader_destination ); + + this.setOutputData( 0, this.properties.type ); + } + + registerShaderNode( "const/const", LGraphShaderConstant ); + + function LGraphShaderVec2() + { + this.addInput("xy","vec2"); + this.addInput("x","float"); + this.addInput("y","float"); + this.addOutput("xy","vec2"); + this.addOutput("x","float"); + this.addOutput("y","float"); + + this.properties = { x: 0, y: 0 }; + } + + LGraphShaderVec2.title = "vec2"; + LGraphShaderVec2.varmodes = ["xy","x","y"]; + + LGraphShaderVec2.prototype.onPropertyChanged = function() + { + this.graph._version++; + } + + LGraphShaderVec2.prototype.onGetCode = function( context ) + { + var props = this.properties; + + var varname = getShaderNodeVarName(this); + var code = " vec2 " + varname + " = " + valueToGLSL([props.x,props.y]) + ";\n"; + + for(var i = 0;i < LGraphShaderVec2.varmodes.length; ++i) + { + var varmode = LGraphShaderVec2.varmodes[i]; + var inlink = getInputLinkID(this,i); + if(!inlink) + continue; + code += " " + varname + "."+varmode+" = " + inlink + ";\n"; + } + + for(var i = 0;i < LGraphShaderVec2.varmodes.length; ++i) + { + var varmode = LGraphShaderVec2.varmodes[i]; + var outlink = getOutputLinkID(this,i); + if(!outlink) + continue; + var type = GLSL_types_const[varmode.length - 1]; + code += " "+type+" " + outlink + " = " + varname + "." + varmode + ";\n"; + this.setOutputData( i, type ); + } + + context.addCode( "code", code, this.shader_destination ); + } + + registerShaderNode( "const/vec2", LGraphShaderVec2 ); + + function LGraphShaderVec3() + { + this.addInput("xyz","vec3"); + this.addInput("x","float"); + this.addInput("y","float"); + this.addInput("z","float"); + this.addInput("xy","vec2"); + this.addInput("xz","vec2"); + this.addInput("yz","vec2"); + this.addOutput("xyz","vec3"); + this.addOutput("x","float"); + this.addOutput("y","float"); + this.addOutput("z","float"); + this.addOutput("xy","vec2"); + this.addOutput("xz","vec2"); + this.addOutput("yz","vec2"); + + this.properties = { x:0, y: 0, z: 0 }; + } + + LGraphShaderVec3.title = "vec3"; + LGraphShaderVec3.varmodes = ["xyz","x","y","z","xy","xz","yz"]; + + LGraphShaderVec3.prototype.onPropertyChanged = function() + { + this.graph._version++; + } + + + LGraphShaderVec3.prototype.onPropertyChanged = function() + { + this.graph._version++; + } + + LGraphShaderVec3.prototype.onGetCode = function( context ) + { + var props = this.properties; + + var varname = getShaderNodeVarName(this); + var code = "vec3 " + varname + " = " + valueToGLSL([props.x,props.y,props.z]) + ";\n"; + + for(var i = 0;i < LGraphShaderVec3.varmodes.length; ++i) + { + var varmode = LGraphShaderVec3.varmodes[i]; + var inlink = getInputLinkID(this,i); + if(!inlink) + continue; + code += " " + varname + "."+varmode+" = " + inlink + ";\n"; + } + + for(var i = 0; i < LGraphShaderVec3.varmodes.length; ++i) + { + var varmode = LGraphShaderVec3.varmodes[i]; + var outlink = getOutputLinkID(this,i); + if(!outlink) + continue; + var type = GLSL_types_const[varmode.length - 1]; + code += " "+type+" " + outlink + " = " + varname + "." + varmode + ";\n"; + this.setOutputData( i, type ); + } + + context.addCode( "code", code, this.shader_destination ); + } + + registerShaderNode( "const/vec3", LGraphShaderVec3 ); + + + function LGraphShaderVec4() + { + this.addInput("xyzw","vec4"); + this.addInput("xyz","vec3"); + this.addInput("x","float"); + this.addInput("y","float"); + this.addInput("z","float"); + this.addInput("w","float"); + this.addInput("xy","vec2"); + this.addInput("yz","vec2"); + this.addInput("zw","vec2"); + this.addOutput("xyzw","vec4"); + this.addOutput("xyz","vec3"); + this.addOutput("x","float"); + this.addOutput("y","float"); + this.addOutput("z","float"); + this.addOutput("xy","vec2"); + this.addOutput("yz","vec2"); + this.addOutput("zw","vec2"); + + this.properties = { x:0, y: 0, z: 0, w: 0 }; + } + + LGraphShaderVec4.title = "vec4"; + LGraphShaderVec4.varmodes = ["xyzw","xyz","x","y","z","w","xy","yz","zw"]; + + LGraphShaderVec4.prototype.onPropertyChanged = function() + { + this.graph._version++; + } + + LGraphShaderVec4.prototype.onGetCode = function( context ) + { + var props = this.properties; + + var varname = getShaderNodeVarName(this); + var code = "vec4 " + varname + " = " + valueToGLSL([props.x,props.y,props.z,props.w]) + ";\n"; + + for(var i = 0;i < LGraphShaderVec4.varmodes.length; ++i) + { + var varmode = LGraphShaderVec4.varmodes[i]; + var inlink = getInputLinkID(this,i); + if(!inlink) + continue; + code += " " + varname + "."+varmode+" = " + inlink + ";\n"; + } + + for(var i = 0;i < LGraphShaderVec4.varmodes.length; ++i) + { + var varmode = LGraphShaderVec4.varmodes[i]; + var outlink = getOutputLinkID(this,i); + if(!outlink) + continue; + var type = GLSL_types_const[varmode.length - 1]; + code += " "+type+" " + outlink + " = " + varname + "." + varmode + ";\n"; + this.setOutputData( i, type ); + } + + context.addCode( "code", code, this.shader_destination ); + + } + + registerShaderNode( "const/vec4", LGraphShaderVec4 ); + //********************************* function LGraphShaderFragColor() { @@ -406,7 +933,7 @@ LGraphShaderFragColor.prototype.onGetCode = function( context ) { - var link_name = getShaderInputLinkName( this, 0 ); + var link_name = getInputLinkID( this, 0 ); if(!link_name) return; @@ -419,11 +946,271 @@ else if(type == "vec3") code = "vec4(" + code + ",1.0);"; - context.addCode("fs_code", "color = " + code + ";\n"); + context.addCode("fs_code", "color = " + code + ";"); } - setShaderNode( LGraphShaderFragColor ); + registerShaderNode( "output/fragcolor", LGraphShaderFragColor ); - LiteGraph.registerNodeType( "shader/fragcolor", LGraphShaderFragColor ); -})(this); \ No newline at end of file + + // ************************************************* + + function LGraphShaderMath() + { + this.addInput("A","float,vec2,vec3,vec4"); + this.addInput("B","float,vec2,vec3,vec4"); + this.addOutput("out",""); + this.properties = { + func: "floor" + }; + this._current = "floor"; + this.addWidget("combo","func",this.properties.func,{ property: "func", values: GLSL_functions_name }); + } + + LGraphShaderMath.title = "Math"; + + LGraphShaderMath.prototype.onPropertyChanged = function(name,value) + { + this.graph._version++; + + if(name == "func") + { + var func_desc = GLSL_functions[ value ]; + if(!func_desc) + return; + + //remove extra inputs + for(var i = func_desc.params.length; i < this.inputs.length; ++i) + this.removeInput(i); + + //add and update inputs + for(var i = 0; i < func_desc.params.length; ++i) + if( this.inputs[i] ) + this.inputs[i].name = func_desc.params[i].name; + else + this.addInput( func_desc.params[i].name, "float,vec2,vec3,vec4" ); + } + } + + LGraphShaderMath.prototype.getTitle = function() + { + if(this.flags.collapsed) + return this.properties.func; + else + return "Func"; + } + + LGraphShaderMath.prototype.onGetCode = function( context ) + { + if(!this.isOutputConnected(0)) + return; + + var inlinks = []; + for(var i = 0; i < 3; ++i) + inlinks.push( { name: getInputLinkID(this,i), type: this.getInputData(i) || "float" } ); + + var outlink = getOutputLinkID(this,0); + if(!outlink) //not connected + return; + + var func_desc = GLSL_functions[ this.properties.func ]; + if(!func_desc) + return; + + //func_desc + var base_type = inlinks[0].type; + var return_type = func_desc.return_type; + if( return_type == "T" ) + return_type = base_type; + + var params = []; + for(var i = 0; i < func_desc.params.length; ++i) + { + var p = func_desc.params[i]; + //if( p.type == "T" && inlinks[i].type != base_type ) + params.push( inlinks[i].name ); + } + + context.addCode("code", return_type + " " + outlink + " = "+func_desc.func+"("+params.join(",")+");", this.shader_destination ); + + this.setOutputData( 0, return_type ); + } + + registerShaderNode( "math/func", LGraphShaderMath ); + + + + function LGraphShaderSnippet() + { + this.addInput("A","float,vec2,vec3,vec4"); + this.addInput("B","float,vec2,vec3,vec4"); + this.addOutput("C","vec4"); + this.properties = { + code:"C = A+B", + type: "vec4" + } + this.addWidget("text","code",this.properties.code,{ property: "code" }); + this.addWidget("combo","type",this.properties.type,{ values:["float","vec2","vec3","vec4"], property: "type" }); + } + + LGraphShaderSnippet.title = "Snippet"; + + LGraphShaderSnippet.prototype.onPropertyChanged = function(name,value) + { + this.graph._version++; + + if(name == "type"&& this.outputs[0].type != value) + { + this.disconnectOutput(0); + this.outputs[0].type = value; + } + } + + LGraphShaderSnippet.prototype.getTitle = function() + { + if(this.flags.collapsed) + return this.properties.code; + else + return "Snippet"; + } + + LGraphShaderSnippet.prototype.onGetCode = function( context ) + { + if(!this.isOutputConnected(0)) + return; + + var inlinkA = getInputLinkID(this,0); + if(!inlinkA) + inlinkA = "1.0"; + var inlinkB = getInputLinkID(this,1); + if(!inlinkB) + inlinkB = "1.0"; + var outlink = getOutputLinkID(this,0); + if(!outlink) //not connected + return; + + var inA_type = this.getInputData(0) || "float"; + var inB_type = this.getInputData(1) || "float"; + var return_type = this.properties.type; + + //cannot resolve input + if(inA_type == "T" || inB_type == "T") + { + return null; + } + + var funcname = "funcSnippet" + this.id; + + var func_code = "\n" + return_type + " " + funcname + "( " + inA_type + " A, " + inB_type + " B) {\n"; + func_code += " " + return_type + " C = " + return_type + "(0.0);\n"; + func_code += " " + this.properties.code + ";\n"; + func_code += " return C;\n}\n"; + + context.addCode("functions", func_code, this.shader_destination ); + context.addCode("code", return_type + " " + outlink + " = "+funcname+"("+inlinkA+","+inlinkB+");", this.shader_destination ); + + this.setOutputData( 0, return_type ); + } + + registerShaderNode( "utils/snippet", LGraphShaderSnippet ); + + + //************************************ + function LGraphShaderRemap() + { + this.addInput("","T,float,vec2,vec3,vec4"); + this.addOutput("","T"); + this.properties = { + min_value: 0, + max_value: 1, + min_value2: 0, + max_value2: 1 + }; + this.addWidget("number","min",0,{ step: 0.1, property: "min_value" }); + this.addWidget("number","max",1,{ step: 0.1, property: "max_value" }); + this.addWidget("number","min2",0,{ step: 0.1, property: "min_value2"}); + this.addWidget("number","max2",1,{ step: 0.1, property: "max_value2"}); + } + + LGraphShaderRemap.title = "Remap"; + + LGraphShaderRemap.prototype.onPropertyChanged = function() + { + this.graph._version++; + } + + LGraphShaderRemap.prototype.onConnectionsChange = function() + { + var return_type = this.getInputDataType(0); + this.outputs[0].type = return_type || "T"; + } + + LGraphShaderRemap.prototype.onGetCode = function( context ) + { + if(!this.isOutputConnected(0)) + return; + + var inlink = getInputLinkID(this,0); + var outlink = getOutputLinkID(this,0); + if(!inlink && !outlink) //not connected + return; + + var return_type = this.getInputDataType(0); + this.outputs[0].type = return_type; + if(return_type == "T") + { + console.warn("node type is T and cannot be resolved"); + return; + } + + if(!inlink) + { + context.addCode("code"," " + return_type + " " + outlink + " = " + return_type + "(0.0);\n"); + return; + } + + var minv = valueToGLSL( this.properties.min_value ); + var maxv = valueToGLSL( this.properties.max_value ); + var minv2 = valueToGLSL( this.properties.min_value2 ); + var maxv2 = valueToGLSL( this.properties.max_value2 ); + + context.addCode("code", return_type + " " + outlink + " = ( (" + inlink + " - "+minv+") / ("+ maxv+" - "+minv+") ) * ("+ maxv2+" - "+minv2+") + " + minv2 + ";", this.shader_destination ); + this.setOutputData( 0, return_type ); + } + + registerShaderNode( "math/remap", LGraphShaderRemap ); + +})(this); + + +/* +// https://blog.undefinist.com/writing-a-shader-graph/ + +\sin +f,Out +float->float +{1} = sin({0}); + +\mul +A,B,Out +T,T->T +T,float->T +{2} = {0} * {1}; + +\clamp +f,min,max,Out +float,float=0,float=1->float +vec2,vec2=vec2(0.0),vec2=vec2(1.0)->vec2 +vec3,vec3=vec3(0.0),vec3=vec3(1.0)->vec3 +vec4,vec4=vec4(0.0),vec4=vec4(1.0)->vec4 +{3}=clamp({0},{1},{2}); + +\mix +A,B,f,Out +float,float,float->float +vec2,vec2,float->vec2 +vec3,vec3,float->vec3 +vec4,vec4,float->vec4 +{3} = mix({0},{1},{2}); + +*/ \ No newline at end of file diff --git a/utils/deploy_files.txt b/utils/deploy_files.txt index 03665ddf7..ce998b646 100755 --- a/utils/deploy_files.txt +++ b/utils/deploy_files.txt @@ -9,6 +9,7 @@ ../src/nodes/logic.js ../src/nodes/graphics.js ../src/nodes/gltextures.js +../src/nodes/shaders.js ../src/nodes/geometry.js ../src/nodes/glfx.js ../src/nodes/midi.js