diff --git a/build/litegraph.js b/build/litegraph.js index db9a36022..64e242148 100644 --- a/build/litegraph.js +++ b/build/litegraph.js @@ -86,7 +86,7 @@ var LiteGraph = global.LiteGraph = { console.log("Node registered: " + type); var categories = type.split("/"); - var classname = base_class.constructor.name; + var classname = base_class.name; var pos = type.lastIndexOf("/"); base_class.category = type.substr(0,pos); @@ -207,7 +207,7 @@ var LiteGraph = global.LiteGraph = { var node = new base_class( title ); node.type = type; - if(!node.title) node.title = title; + if(!node.title && title) node.title = title; if(!node.properties) node.properties = {}; if(!node.properties_info) node.properties_info = []; if(!node.flags) node.flags = {}; @@ -338,7 +338,10 @@ var LiteGraph = global.LiteGraph = { !type_b || //generic input type_a == type_b || //same type (is valid for triggers) type_a == LiteGraph.EVENT && type_b == LiteGraph.ACTION ) - return true; + return true; + + type_a = String(type_a) //* Enforce string type to handle toLowerCase call (-1 number not ok) + type_b = String(type_b) type_a = type_a.toLowerCase(); type_b = type_b.toLowerCase(); @@ -440,7 +443,7 @@ LGraph.prototype.clear = function() this.catch_errors = true; - //globals + //subgraph_data this.global_inputs = {}; this.global_outputs = {}; @@ -728,8 +731,23 @@ LGraph.prototype.computeExecutionOrder = function( only_onExecute, set_level ) if( L.length != this._nodes.length && LiteGraph.debug ) console.warn("something went wrong, nodes missing"); + var l = L.length; + //save order number in the node - for(var i = 0; i < L.length; ++i) + for(var i = 0; i < l; ++i) + L[i].order = i; + + //sort now by priority + L = L.sort(function(A,B){ + var Ap = A.constructor.priority || A.priority || 0; + var Bp = B.constructor.priority || B.priority || 0; + if(Ap == Bp) + return A.order - B.order; + return Ap - Bp; + }); + + //save order number in the node, again... + for(var i = 0; i < l; ++i) L[i].order = i; return L; @@ -1028,7 +1046,7 @@ LGraph.prototype.findNodesByType = function(type) /** * Returns a list of nodes that matches a name -* @method findNodesByName +* @method findNodesByTitle * @param {String} name the name of the node to search * @return {Array} a list with all the nodes with this name */ @@ -1050,7 +1068,6 @@ LGraph.prototype.findNodesByTitle = function(title) * @param {Array} nodes_list a list with all the nodes to search from, by default is all the nodes in the graph * @return {Array} a list with all the nodes that intersect this coordinate */ - LGraph.prototype.getNodeOnPos = function(x,y, nodes_list) { nodes_list = nodes_list || this._nodes; @@ -1065,7 +1082,13 @@ LGraph.prototype.getNodeOnPos = function(x,y, nodes_list) // ********** GLOBALS ***************** -//Tell this graph has a global input of this type +/** +* Tell this graph it has a global graph input of this type +* @method addGlobalInput +* @param {String} name +* @param {String} type +* @param {*} value [optional] +*/ LGraph.prototype.addGlobalInput = function(name, type, value) { this.global_inputs[name] = { name: name, type: type, value: value }; @@ -1077,7 +1100,12 @@ LGraph.prototype.addGlobalInput = function(name, type, value) this.onGlobalsChange(); } -//assign a data to the global input +/** +* Assign a data to the global graph input +* @method setGlobalInputData +* @param {String} name +* @param {*} data +*/ LGraph.prototype.setGlobalInputData = function(name, data) { var input = this.global_inputs[name]; @@ -1086,7 +1114,21 @@ LGraph.prototype.setGlobalInputData = function(name, data) input.value = data; } -//assign a data to the global input +/** +* Assign a data to the global graph input (same as setGlobalInputData) +* @method setInputData +* @param {String} name +* @param {*} data +*/ +LGraph.prototype.setInputData = LGraph.prototype.setGlobalInputData; + + +/** +* Returns the current value of a global graph input +* @method getGlobalInputData +* @param {String} name +* @return {*} the data +*/ LGraph.prototype.getGlobalInputData = function(name) { var input = this.global_inputs[name]; @@ -1095,7 +1137,12 @@ LGraph.prototype.getGlobalInputData = function(name) return input.value; } -//rename the global input +/** +* Changes the name of a global graph input +* @method renameGlobalInput +* @param {String} old_name +* @param {String} new_name +*/ LGraph.prototype.renameGlobalInput = function(old_name, name) { if(name == old_name) @@ -1120,6 +1167,12 @@ LGraph.prototype.renameGlobalInput = function(old_name, name) this.onGlobalsChange(); } +/** +* Changes the type of a global graph input +* @method changeGlobalInputType +* @param {String} name +* @param {String} type +*/ LGraph.prototype.changeGlobalInputType = function(name, type) { if(!this.global_inputs[name]) @@ -1133,6 +1186,12 @@ LGraph.prototype.changeGlobalInputType = function(name, type) this.onGlobalInputTypeChanged(name, type); } +/** +* Removes a global graph input +* @method removeGlobalInput +* @param {String} name +* @param {String} type +*/ LGraph.prototype.removeGlobalInput = function(name) { if(!this.global_inputs[name]) @@ -1148,7 +1207,13 @@ LGraph.prototype.removeGlobalInput = function(name) return true; } - +/** +* Creates a global graph output +* @method addGlobalOutput +* @param {String} name +* @param {String} type +* @param {*} value +*/ LGraph.prototype.addGlobalOutput = function(name, type, value) { this.global_outputs[name] = { name: name, type: type, value: value }; @@ -1160,7 +1225,12 @@ LGraph.prototype.addGlobalOutput = function(name, type, value) this.onGlobalsChange(); } -//assign a data to the global output +/** +* Assign a data to the global output +* @method setGlobalOutputData +* @param {String} name +* @param {String} value +*/ LGraph.prototype.setGlobalOutputData = function(name, value) { var output = this.global_outputs[ name ]; @@ -1169,7 +1239,12 @@ LGraph.prototype.setGlobalOutputData = function(name, value) output.value = value; } -//assign a data to the global input +/** +* Returns the current value of a global graph output +* @method getGlobalOutputData +* @param {String} name +* @return {*} the data +*/ LGraph.prototype.getGlobalOutputData = function(name) { var output = this.global_outputs[name]; @@ -1178,8 +1253,21 @@ LGraph.prototype.getGlobalOutputData = function(name) return output.value; } +/** +* Returns the current value of a global graph output (sames as getGlobalOutputData) +* @method getOutputData +* @param {String} name +* @return {*} the data +*/ +LGraph.prototype.getOutputData = LGraph.prototype.getGlobalOutputData; -//rename the global output + +/** +* Renames a global graph output +* @method renameGlobalOutput +* @param {String} old_name +* @param {String} new_name +*/ LGraph.prototype.renameGlobalOutput = function(old_name, name) { if(!this.global_outputs[old_name]) @@ -1201,6 +1289,12 @@ LGraph.prototype.renameGlobalOutput = function(old_name, name) this.onGlobalsChange(); } +/** +* Changes the type of a global graph output +* @method changeGlobalOutputType +* @param {String} name +* @param {String} type +*/ LGraph.prototype.changeGlobalOutputType = function(name, type) { if(!this.global_outputs[name]) @@ -1214,6 +1308,11 @@ LGraph.prototype.changeGlobalOutputType = function(name, type) this.onGlobalOutputTypeChanged(name, type); } +/** +* Removes a global graph output +* @method removeGlobalOutput +* @param {String} name +*/ LGraph.prototype.removeGlobalOutput = function(name) { if(!this.global_outputs[name]) @@ -1228,49 +1327,16 @@ LGraph.prototype.removeGlobalOutput = function(name) return true; } - -/** -* Assigns a value to all the nodes that matches this name. This is used to create global variables of the node that -* can be easily accesed from the outside of the graph -* @method setInputData -* @param {String} name the name of the node -* @param {*} value value to assign to this node -*/ - -LGraph.prototype.setInputData = function(name,value) -{ - var nodes = this.findNodesByName( name ); - for(var i = 0, l = nodes.length; i < l; ++i) - nodes[i].setValue(value); -} - -/** -* Returns the value of the first node with this name. This is used to access global variables of the graph from the outside -* @method setInputData -* @param {String} name the name of the node -* @return {*} value of the node -*/ - -LGraph.prototype.getOutputData = function(name) -{ - var n = this.findNodesByName(name); - if(n.length) - return m[0].getValue(); - return null; -} - -//This feature is not finished yet, is to create graphs where nodes are not executed unless a trigger message is received - LGraph.prototype.triggerInput = function(name,value) { - var nodes = this.findNodesByName(name); + var nodes = this.findNodesByTitle(name); for(var i = 0; i < nodes.length; ++i) nodes[i].onTrigger(value); } LGraph.prototype.setCallback = function(name,func) { - var nodes = this.findNodesByName(name); + var nodes = this.findNodesByTitle(name); for(var i = 0; i < nodes.length; ++i) nodes[i].setTrigger(func); } @@ -1494,7 +1560,7 @@ LGraph.prototype.onNodeTrace = function(node, msg, color) function LGraphNode(title) { - this._ctor(); + this._ctor(title); } global.LGraphNode = LiteGraph.LGraphNode = LGraphNode; @@ -1566,6 +1632,7 @@ LGraphNode.prototype.configure = function(info) if(info[j] == null) continue; + else if (typeof(info[j]) == 'object') //object { if(this[j] && this[j].configure) @@ -1577,6 +1644,9 @@ LGraphNode.prototype.configure = function(info) this[j] = info[j]; } + if(!info.title) + this.title = this.constructor.title; + if(this.onConnectionsChange) { if(this.inputs) @@ -1644,25 +1714,31 @@ LGraphNode.prototype.configure = function(info) LGraphNode.prototype.serialize = function() { - //clear outputs last data (because data in connections is never serialized but stored inside the outputs info) - if(this.outputs) - for(var i = 0; i < this.outputs.length; i++) - delete this.outputs[i]._data; - //create serialization object var o = { id: this.id, - title: this.title, type: this.type, pos: this.pos, size: this.size, data: this.data, flags: LiteGraph.cloneObject(this.flags), - inputs: this.inputs, - outputs: this.outputs, mode: this.mode }; + if(this.inputs) + o.inputs = this.inputs; + + if(this.outputs) + { + //clear outputs last data (because data in connections is never serialized but stored inside the outputs info) + for(var i = 0; i < this.outputs.length; i++) + delete this.outputs[i]._data; + o.outputs = this.outputs; + } + + if( this.title && this.title != this.constructor.title ) + o.title = this.title; + if(this.properties) o.properties = LiteGraph.cloneObject(this.properties); @@ -1775,7 +1851,7 @@ LGraphNode.prototype.setOutputData = function(slot, data) } /** -* retrieves the input data (data traveling through the connection) from one slot +* Retrieves the input data (data traveling through the connection) from one slot * @method getInputData * @param {number} slot * @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link @@ -1794,10 +1870,10 @@ LGraphNode.prototype.getInputData = function( slot, force_update ) if(!link) //bug: weird case but it happens sometimes return null; - //used to extract data from the incomming connection if(!force_update) return link.data; + //special case: used to extract data from the incomming connection before the graph has been executed var node = this.graph.getNodeById( link.origin_id ); if(!node) return link.data; @@ -1810,6 +1886,22 @@ LGraphNode.prototype.getInputData = function( slot, force_update ) return link.data; } +/** +* Retrieves the input data from one slot using its name instead of slot number +* @method getInputDataByName +* @param {String} slot_name +* @param {boolean} force_update if set to true it will force the connected node of this slot to output data into this link +* @return {*} data or if it is not connected returns null +*/ +LGraphNode.prototype.getInputDataByName = function( slot_name, force_update ) +{ + var slot = this.findInputSlot( slot_name ); + if( slot == -1 ) + return null; + return this.getInputData( slot, force_update ); +} + + /** * tells you if there is a connection in one input slot * @method isInputConnected @@ -1859,6 +1951,31 @@ LGraphNode.prototype.getInputNode = function( slot ) return this.graph.getNodeById( link_info.origin_id ); } + +/** +* returns the value of an input with this name, otherwise checks if there is a property with that name +* @method getInputOrProperty +* @param {string} name +* @return {*} value +*/ +LGraphNode.prototype.getInputOrProperty = function( name ) +{ + if(!this.inputs || !this.inputs.length) + return this.properties ? this.properties[name] : null; + + for(var i = 0, l = this.inputs.length; i < l; ++i) + if(name == this.inputs[i].name) + { + var link_id = this.inputs[i].link; + var link = this.graph.links[ link_id ]; + return link ? link.data : null; + } + return this.properties[name]; +} + + + + /** * tells you the last output data that went in that slot * @method getOutputData @@ -1902,10 +2019,26 @@ LGraphNode.prototype.getOutputInfo = function(slot) LGraphNode.prototype.isOutputConnected = function(slot) { if(!this.outputs) - return null; + return false; return (slot < this.outputs.length && this.outputs[slot].links && this.outputs[slot].links.length); } +/** +* tells you if there is any connection in the output slots +* @method isAnyOutputConnected +* @return {boolean} +*/ +LGraphNode.prototype.isAnyOutputConnected = function() +{ + if(!this.outputs) + return false; + for(var i = 0; i < this.outputs.length; ++i) + if( this.outputs[i].links && this.outputs[i].links.length ) + return true; + return false; +} + + /** * retrieves all the nodes connected to this output slot * @method getOutputNodes @@ -2812,10 +2945,10 @@ function LGraphCanvas( canvas, graph, options ) this.node_title_color = LiteGraph.NODE_TITLE_COLOR; this.default_link_color = "#AAC"; this.default_connection_color = { - input_off: "#AAC", + input_off: "#AAB", input_on: "#7F7", - output_off: "#AAC", - output_on: "#7F7", + output_off: "#AAB", + output_on: "#7F7" }; this.highquality_render = true; @@ -6162,6 +6295,17 @@ function ContextMenu( values, options ) } }, true); + function on_mouse_wheel(e) + { + var pos = parseInt( root.style.top ); + root.style.top = (pos + e.deltaY * 0.1).toFixed() + "px"; + e.preventDefault(); + return true; + } + + root.addEventListener("wheel", on_mouse_wheel, true); + root.addEventListener("mousewheel", on_mouse_wheel, true); + this.root = root; @@ -6455,6 +6599,7 @@ LiteGraph.extendClass = function ( target, origin ) } } +//used to create nodes from wrapping functions LiteGraph.getParameterNames = function(func) { return (func + '') .replace(/[/][/].*$/mg,'') // strip single-line comments @@ -6465,6 +6610,8 @@ LiteGraph.getParameterNames = function(func) { .split(',').filter(Boolean); // split & filter [""] } +Math.clamp = function(v,a,b) { return (a > v ? a : (b < v ? b : v)); } + if( typeof(window) != "undefined" && !window["requestAnimationFrame"] ) { window.requestAnimationFrame = window.webkitRequestAnimationFrame || @@ -6521,7 +6668,6 @@ function Subgraph() this.subgraph.onGlobalOutputRenamed = this.onSubgraphRenamedGlobalOutput.bind(this); this.subgraph.onGlobalOutputTypeChanged = this.onSubgraphTypeChangeGlobalOutput.bind(this); - this.bgcolor = "#663"; } @@ -6717,6 +6863,8 @@ function GlobalOutput() this.addInput(output_name, null); + this._value = null; + this.properties = {name: output_name, type: null }; var that = this; @@ -6751,7 +6899,7 @@ function GlobalOutput() }); } -GlobalOutput.title = "Ouput"; +GlobalOutput.title = "Output"; GlobalOutput.desc = "Output of the graph"; GlobalOutput.prototype.onAdded = function() @@ -6759,9 +6907,15 @@ GlobalOutput.prototype.onAdded = function() var name = this.graph.addGlobalOutput( this.properties.name, this.properties.type ); } +GlobalOutput.prototype.getValue = function() +{ + return this._value; +} + GlobalOutput.prototype.onExecute = function() { - this.graph.setGlobalOutputData( this.properties.name, this.getInputData(0) ); + this._value = this.getInputData(0); + this.graph.setGlobalOutputData( this.properties.name, this._value ); } LiteGraph.registerNodeType("graph/output", GlobalOutput); @@ -7172,6 +7326,63 @@ var LiteGraph = global.LiteGraph; LiteGraph.registerNodeType("widget/button", WidgetButton ); + + function WidgetToggle() + { + this.addInput( "", "boolean" ); + this.addOutput( "v", "boolean" ); + this.addOutput( "e", LiteGraph.EVENT ); + this.properties = { font: "", value: false }; + this.size = [124,64]; + } + + WidgetToggle.title = "Toggle"; + WidgetToggle.desc = "Toggles between true or false"; + + WidgetToggle.prototype.onDrawForeground = function(ctx) + { + if(this.flags.collapsed) + return; + + var size = this.size[1] * 0.5; + var margin = 0.25; + var h = this.size[1] * 0.8; + + ctx.fillStyle = "#AAA"; + ctx.fillRect(10, h - size,size,size); + + ctx.fillStyle = this.properties.value ? "#AEF" : "#000"; + ctx.fillRect(10+size*margin,h - size + size*margin,size*(1-margin*2),size*(1-margin*2)); + + ctx.textAlign = "left"; + ctx.font = this.properties.font || ((size * 0.8).toFixed(0) + "px Arial"); + ctx.fillStyle = "#AAA"; + ctx.fillText( this.title, size + 20, h * 0.85 ); + ctx.textAlign = "left"; + } + + WidgetToggle.prototype.onExecute = function() + { + var v = this.getInputData(0); + if( v != null ) + this.properties.value = v; + this.setOutputData( 0, this.properties.value ); + } + + WidgetToggle.prototype.onMouseDown = function(e, local_pos) + { + if(local_pos[0] > 1 && local_pos[1] > 1 && local_pos[0] < (this.size[0] - 2) && local_pos[1] < (this.size[1] - 2) ) + { + this.properties.value = !this.properties.value; + this.trigger( "clicked", this.properties.value ); + return true; + } + } + + LiteGraph.registerNodeType("widget/toggle", WidgetToggle ); + + + /* Knob ****************/ function WidgetKnob() @@ -8472,7 +8683,7 @@ MathScale.prototype.onExecute = function() LiteGraph.registerNodeType("math/scale", MathScale ); -//Math clamp +//Math Average function MathAverageFilter() { this.addInput("in","number"); @@ -8523,6 +8734,35 @@ MathAverageFilter.prototype.onPropertyChanged = function( name, value ) LiteGraph.registerNodeType("math/average", MathAverageFilter ); +//Math +function MathTendTo() +{ + this.addInput("in","number"); + this.addOutput("out","number"); + this.addProperty( "factor", 0.1 ); + this.size = [60,20]; + this._value = null; +} + +MathTendTo.title = "TendTo"; +MathTendTo.desc = "moves the output value always closer to the input"; + +MathTendTo.prototype.onExecute = function() +{ + var v = this.getInputData(0); + if(v == null) + v = 0; + var f = this.properties.factor; + if(this._value == null) + this._value = v; + else + this._value = this._value * (1 - f) + v * f; + this.setOutputData( 0, this._value ); +} + +LiteGraph.registerNodeType("math/tendTo", MathTendTo ); + + //Math operation function MathOperation() { @@ -9181,9 +9421,80 @@ LiteGraph.registerNodeType("logic/selector", Selector); (function(global){ var LiteGraph = global.LiteGraph; +function GraphicsPlot() +{ + this.addInput("A","Number"); + this.addInput("B","Number"); + this.addInput("C","Number"); + this.addInput("D","Number"); + + this.values = [[],[],[],[]]; + this.properties = { scale: 2 }; +} + +GraphicsPlot.title = "Plot"; +GraphicsPlot.desc = "Plots data over time"; +GraphicsPlot.colors = ["#FFF","#F99","#9F9","#99F"]; + +GraphicsPlot.prototype.onExecute = function(ctx) +{ + if(this.flags.collapsed) + return; + + var size = this.size; + + for(var i = 0; i < 4; ++i) + { + var v = this.getInputData(i); + if(v == null) + continue; + var values = this.values[i]; + values.push(v); + if(values.length > size[0]) + values.shift(); + } +} + +GraphicsPlot.prototype.onDrawBackground = function(ctx) +{ + if(this.flags.collapsed) + return; + + var size = this.size; + + var scale = 0.5 * size[1] / this.properties.scale; + var colors = GraphicsPlot.colors; + var offset = size[1] * 0.5; + + ctx.fillStyle = "#000"; + ctx.fillRect(0,0, size[0],size[1]); + ctx.strokeStyle = "#555"; + ctx.beginPath(); + ctx.moveTo(0, offset); + ctx.lineTo(size[0], offset); + ctx.stroke(); + + for(var i = 0; i < 4; ++i) + { + var values = this.values[i]; + ctx.strokeStyle = colors[i]; + ctx.beginPath(); + var v = values[0] * scale * -1 + offset; + ctx.moveTo(0, Math.clamp( v, 0, size[1]) ); + for(var j = 1; j < values.length && j < size[0]; ++j) + { + var v = values[j] * scale * -1 + offset; + ctx.lineTo( j, Math.clamp( v, 0, size[1]) ); + } + ctx.stroke(); + } +} + +LiteGraph.registerNodeType("graphics/plot", GraphicsPlot); + + function GraphicsImage() { - this.inputs = []; this.addOutput("frame","image"); this.properties = {"url":""}; } @@ -9240,11 +9551,8 @@ GraphicsImage.prototype.loadImage = function( url, callback ) this.img = document.createElement("img"); - if(url.substr(0,7) == "http://") - { - if(LiteGraph.proxy) //proxy external files - url = LiteGraph.proxy + url.substr(7); - } + if(url.substr(0,4) == "http" && LiteGraph.proxy) + url = LiteGraph.proxy + url.substr( url.indexOf(":") + 3 ); this.img.src = url; this.boxcolor = "#F95"; @@ -9623,7 +9931,7 @@ function ImageVideo() { this.addInput("t","number"); this.addOutputs([["frame","image"],["t","number"],["d","number"]]); - this.properties = {"url":""}; + this.properties = { url:"", use_proxy: true }; } ImageVideo.title = "Video"; @@ -9669,6 +9977,9 @@ ImageVideo.prototype.loadVideo = function(url) { this._video_url = url; + if(this.properties.use_proxy && url.substr(0,4) == "http" && LiteGraph.proxy ) + url = LiteGraph.proxy + url.substr( url.indexOf(":") + 3 ); + this._video = document.createElement("video"); this._video.src = url; this._video.type = "type=video/mp4"; @@ -9995,6 +10306,18 @@ if(typeof(GL) != "undefined") return target; } + + LGraphTexture.getTextureType = function( precision, ref_texture ) + { + var type = ref_texture ? ref_texture.type : gl.UNSIGNED_BYTE; + switch( precision ) + { + case LGraphTexture.LOW: type = gl.UNSIGNED_BYTE; break; + case LGraphTexture.HIGH: type = gl.HIGH_PRECISION_FORMAT; break; + } + return type; + } + LGraphTexture.getNoiseTexture = function() { if(this._noise_texture) @@ -10351,33 +10674,24 @@ if(typeof(GL) != "undefined") var width = 512; var height = 512; - var type = gl.UNSIGNED_BYTE; if(tex) { width = tex.width; height = tex.height; - type = tex.type; } else if (texB) { width = texB.width; height = texB.height; - type = texB.type; } + var type = LGraphTexture.getTextureType( this.properties.precision, tex ); + if(!tex && !this._tex ) - this._tex = new GL.Texture( width, height, { type: this.precision === LGraphTexture.LOW ? gl.UNSIGNED_BYTE : gl.HIGH_PRECISION_FORMAT, format: gl.RGBA, filter: gl.LINEAR }); + this._tex = new GL.Texture( width, height, { type: type, format: gl.RGBA, filter: gl.LINEAR }); else this._tex = LGraphTexture.getTargetTexture( tex || this._tex, this._tex, this.properties.precision ); - /* - if(this.properties.low_precision) - type = gl.UNSIGNED_BYTE; - - if(!this._tex || this._tex.width != width || this._tex.height != height || this._tex.type != type ) - this._tex = new GL.Texture( width, height, { type: type, format: gl.RGBA, filter: gl.LINEAR }); - */ - var uvcode = ""; if(this.properties.uvcode) { @@ -10474,9 +10788,10 @@ if(typeof(GL) != "undefined") function LGraphTextureShader() { this.addOutput("Texture","Texture"); - this.properties = {code:"", width: 512, height: 512}; + this.properties = {code:"", width: 512, height: 512, precision: LGraphTexture.DEFAULT }; this.properties.code = "\nvoid main() {\n vec2 uv = v_coord;\n vec3 color = vec3(0.0);\n//your code here\n\ngl_FragColor = vec4(color, 1.0);\n}\n"; + this._uniforms = { texSize: vec2.create(), time: time }; } LGraphTextureShader.title = "Shader"; @@ -10609,16 +10924,25 @@ if(typeof(GL) != "undefined") shader.setUniform( info.name, data ); } + var uniforms = this._uniforms; + + var type = LGraphTexture.getTextureType( this.properties.precision ); + //render to texture - if(!this._tex || this._tex.width != this.properties.width || this._tex.height != this.properties.height ) - this._tex = new GL.Texture( this.properties.width, this.properties.height, { format: gl.RGBA, filter: gl.LINEAR }); + var w = this.properties.width|0; + var h = this.properties.height|0; + uniforms.texSize[0] = w; + uniforms.texSize[1] = h; + + if(!this._tex || this._tex.type != type || this._tex.width != w || this._tex.height != h ) + this._tex = new GL.Texture( w, h, { type: type, format: gl.RGBA, filter: gl.LINEAR }); var tex = this._tex; var time = this.graph.getTime(); - tex.drawTo(function() { - shader.uniforms({texSize: [tex.width, tex.height], time: time}).draw( Mesh.getScreenQuad() ); + tex.drawTo(function() { + shader.uniforms( uniforms ).draw( GL.Mesh.getScreenQuad() ); }); - this.setOutputData(0, this._tex); + this.setOutputData( 0, this._tex ); } LGraphTextureShader.pixel_shader = "precision highp float;\n\ @@ -11139,10 +11463,13 @@ if(typeof(GL) != "undefined") function LGraphTextureAverage() { this.addInput("Texture","Texture"); - this.addOutput("","Texture"); + this.addOutput("tex","Texture"); + this.addOutput("avg","vec4"); + this.addOutput("lum","number"); this.properties = { mipmap_offset: 0, low_precision: false }; this._uniforms = { u_texture: 0, u_mipmap_offset: this.properties.mipmap_offset }; + this._luminance = new Float32Array(4); } LGraphTextureAverage.title = "Average"; @@ -11154,7 +11481,7 @@ if(typeof(GL) != "undefined") if(!tex) return; - if(!this.isOutputConnected(0)) + if(!this.isOutputConnected(0) && !this.isOutputConnected(1) && !this.isOutputConnected(2)) return; //saves work if(!LGraphTextureAverage._shader) @@ -11168,7 +11495,10 @@ if(typeof(GL) != "undefined") } var temp = this._temp_texture; - var type = this.properties.low_precision ? gl.UNSIGNED_BYTE : tex.type; + var type = gl.UNSIGNED_BYTE; + if(tex.type != type) //force floats, half floats cannot be read with gl.readPixels + type = gl.FLOAT; + if(!temp || temp.type != type ) this._temp_texture = new GL.Texture( 1, 1, { type: type, format: gl.RGBA, filter: gl.NEAREST }); @@ -11180,6 +11510,24 @@ if(typeof(GL) != "undefined") }); this.setOutputData(0,this._temp_texture); + + if(this.isOutputConnected(1) || this.isOutputConnected(2)) + { + var pixel = this._temp_texture.getPixels(); + if(pixel) + { + var v = this._luminance; + var type = this._temp_texture.type; + v.set( pixel ); + if(type == gl.UNSIGNED_BYTE) + vec4.scale( v,v, 1/255 ); + else if(type == GL.HALF_FLOAT || type == GL.HALF_FLOAT_OES) + vec4.scale( v,v, 1/(255*255) ); //is this correct? + this.setOutputData(1,v); + this.setOutputData(2,(v[0] + v[1] + v[2]) / 3); + } + + } } LGraphTextureAverage.pixel_shader = "precision highp float;\n\ @@ -11243,7 +11591,7 @@ if(typeof(GL) != "undefined") } catch(err) { - console.error("image comes from an unsafe location, cannot be uploaded to webgl"); + console.error("image comes from an unsafe location, cannot be uploaded to webgl: " + err); return; } @@ -11267,12 +11615,12 @@ if(typeof(GL) != "undefined") } LGraphTextureLUT.widgets_info = { + "texture": { widget:"texture"}, "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES } }; LGraphTextureLUT.title = "LUT"; LGraphTextureLUT.desc = "Apply LUT to Texture"; - LGraphTextureLUT.widgets_info = {"texture": { widget:"texture"} }; LGraphTextureLUT.prototype.onExecute = function() { @@ -11287,7 +11635,8 @@ if(typeof(GL) != "undefined") return; } - if(!tex) return; + if(!tex) + return; var lut_tex = this.getInputData(1); @@ -11662,7 +12011,7 @@ if(typeof(GL) != "undefined") this.addInput("Tex.","Texture"); this.addOutput("Edges","Texture"); - this.properties = { invert: true, factor: 1, precision: LGraphTexture.DEFAULT }; + this.properties = { invert: true, threshold: false, factor: 1, precision: LGraphTexture.DEFAULT }; if(!LGraphTextureEdges._shader) LGraphTextureEdges._shader = new GL.Shader( Shader.SCREEN_VERTEX_SHADER, LGraphTextureEdges.pixel_shader ); @@ -11699,10 +12048,11 @@ if(typeof(GL) != "undefined") var shader = LGraphTextureEdges._shader; var invert = this.properties.invert; var factor = this.properties.factor; + var threshold = this.properties.threshold ? 1 : 0; this._tex.drawTo( function() { tex.bind(0); - shader.uniforms({u_texture:0, u_isize:[1/tex.width,1/tex.height], u_factor: factor, u_invert: invert ? 1 : 0}).draw(mesh); + shader.uniforms({u_texture:0, u_isize:[1/tex.width,1/tex.height], u_factor: factor, u_threshold: threshold, u_invert: invert ? 1 : 0}).draw(mesh); }); this.setOutputData(0, this._tex); @@ -11715,6 +12065,7 @@ if(typeof(GL) != "undefined") uniform vec2 u_isize;\n\ uniform int u_invert;\n\ uniform float u_factor;\n\ + uniform float u_threshold;\n\ \n\ void main() {\n\ vec4 center = texture2D(u_texture, v_coord);\n\ @@ -11726,7 +12077,10 @@ if(typeof(GL) != "undefined") diff *= u_factor;\n\ if(u_invert == 1)\n\ diff.xyz = vec3(1.0) - diff.xyz;\n\ - gl_FragColor = vec4( diff.xyz, center.a );\n\ + if( u_threshold == 0.0 )\n\ + gl_FragColor = vec4( diff.xyz, center.a );\n\ + else\n\ + gl_FragColor = vec4( diff.x > 0.5 ? 1.0 : 0.0, diff.y > 0.5 ? 1.0 : 0.0, diff.z > 0.5 ? 1.0 : 0.0, center.a );\n\ }\n\ "; @@ -11808,7 +12162,8 @@ if(typeof(GL) != "undefined") shader.uniforms( uniforms ).draw(mesh); }); - this.setOutputData(0, this._temp_texture); + this._temp_texture.near_far_planes = planes; + this.setOutputData(0, this._temp_texture ); } LGraphTextureDepthRange.pixel_shader = "precision highp float;\n\ @@ -11851,12 +12206,16 @@ if(typeof(GL) != "undefined") this.addInput("Iterations","number"); this.addInput("Intensity","number"); this.addOutput("Blurred","Texture"); - this.properties = { intensity: 1, iterations: 1, preserve_aspect: false, scale:[1,1] }; + this.properties = { intensity: 1, iterations: 1, preserve_aspect: false, scale:[1,1], precision: LGraphTexture.DEFAULT }; } LGraphTextureBlur.title = "Blur"; LGraphTextureBlur.desc = "Blur a texture"; + LGraphTextureBlur.widgets_info = { + precision: { widget:"combo", values: LGraphTexture.MODE_VALUES } + }; + LGraphTextureBlur.max_iterations = 20; LGraphTextureBlur.prototype.onExecute = function() @@ -11868,13 +12227,13 @@ if(typeof(GL) != "undefined") if(!this.isOutputConnected(0)) return; //saves work - var temp = this._temp_texture; + var temp = this._final_texture; if(!temp || temp.width != tex.width || temp.height != tex.height || temp.type != tex.type ) { //we need two textures to do the blurring - this._temp_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR }); - this._final_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR }); + //this._temp_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR }); + temp = this._final_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR }); } //iterations @@ -11906,18 +12265,15 @@ if(typeof(GL) != "undefined") aspect = 1; aspect = this.properties.preserve_aspect ? aspect : 1; - var start_texture = tex; var scale = this.properties.scale || [1,1]; - var origin = start_texture; - for(var i = 0; i < iterations; ++i) - { - origin.applyBlur( aspect * scale[0] * i, scale[1] * i, intensity, this._temp_texture, this._final_texture ); - origin = this._final_texture; - } + tex.applyBlur( aspect * scale[0], scale[1], intensity, temp ); + for(var i = 1; i < iterations; ++i) + temp.applyBlur( aspect * scale[0] * (i+1), scale[1] * (i+1), intensity ); - this.setOutputData(0, this._final_texture); + this.setOutputData(0, temp ); } + /* LGraphTextureBlur.pixel_shader = "precision highp float;\n\ precision highp float;\n\ varying vec2 v_coord;\n\ @@ -11937,12 +12293,237 @@ if(typeof(GL) != "undefined") sum += texture2D(u_texture, v_coord + u_offset * 2.0) * 0.12/0.98;\n\ sum += texture2D(u_texture, v_coord + u_offset * 1.0) * 0.15/0.98;\n\ gl_FragColor = u_intensity * sum;\n\ - /*gl_FragColor.a = center.a*/;\n\ }\n\ "; + */ LiteGraph.registerNodeType("texture/blur", LGraphTextureBlur ); + + // Texture Glow ***************************************** + //based in https://catlikecoding.com/unity/tutorials/advanced-rendering/bloom/ + function LGraphTextureGlow() + { + this.addInput("in","Texture"); + this.addInput("dirt","Texture"); + this.addOutput("out","Texture"); + this.addOutput("glow","Texture"); + this.properties = { enabled: true, intensity: 1, persistence: 0.99, iterations:16, threshold:0, scale: 1, dirt_factor: 0.5, precision: LGraphTexture.DEFAULT }; + this._textures = []; + this._uniforms = { u_intensity: 1, u_texture: 0, u_glow_texture: 1, u_threshold: 0, u_texel_size: vec2.create() }; + } + + LGraphTextureGlow.title = "Glow"; + LGraphTextureGlow.desc = "Filters a texture giving it a glow effect"; + LGraphTextureGlow.weights = new Float32Array( [0.5,0.4,0.3,0.2] ); + + LGraphTextureGlow.widgets_info = { + "iterations": { type:"number", min: 0, max: 16, step: 1, precision: 0 }, + "threshold": { type:"number", min: 0, max: 10, step: 0.01, precision: 2 }, + "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphTextureGlow.prototype.onGetInputs = function(){ + return [["enabled","boolean"],["threshold","number"],["intensity","number"],["persistence","number"],["iterations","number"],["dirt_factor","number"]]; + } + + LGraphTextureGlow.prototype.onGetOutputs = function(){ + return [["average","Texture"]]; + } + + LGraphTextureGlow.prototype.onExecute = function() + { + var tex = this.getInputData(0); + if(!tex) + return; + + if(!this.isAnyOutputConnected()) + return; //saves work + + if(this.properties.precision === LGraphTexture.PASS_THROUGH || this.getInputOrProperty("enabled" ) === false ) + { + this.setOutputData(0,tex); + return; + } + + var width = tex.width; + var height = tex.height; + + var texture_info = { format: tex.format, type: tex.type, minFilter: GL.LINEAR, magFilter: GL.LINEAR, wrap: gl.CLAMP_TO_EDGE }; + var type = LGraphTexture.getTextureType( this.properties.precision, tex ); + + var uniforms = this._uniforms; + var textures = this._textures; + + //cut + var shader = LGraphTextureGlow._cut_shader; + if(!shader) + shader = LGraphTextureGlow._cut_shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureGlow.cut_pixel_shader ); + + gl.disable( gl.DEPTH_TEST ); + gl.disable( gl.BLEND ); + + uniforms.u_threshold = this.getInputOrProperty("threshold"); + var currentDestination = textures[0] = GL.Texture.getTemporary( width, height, texture_info ); + tex.blit( currentDestination, shader.uniforms(uniforms) ); + var currentSource = currentDestination; + + var iterations = this.getInputOrProperty("iterations"); + iterations = Math.clamp( iterations, 1, 16) | 0; + var texel_size = uniforms.u_texel_size; + var intensity = this.getInputOrProperty("intensity"); + + uniforms.u_intensity = 1; + uniforms.u_delta = this.properties.scale; //1 + + //downscale/upscale shader + var shader = LGraphTextureGlow._shader; + if(!shader) + shader = LGraphTextureGlow._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureGlow.scale_pixel_shader ); + + var i = 1; + //downscale + for (;i < iterations; i++) { + width = width>>1; + if( (height|0) > 1 ) + height = height>>1; + if( width < 2 ) + break; + currentDestination = textures[i] = GL.Texture.getTemporary( width, height, texture_info ); + texel_size[0] = 1 / currentSource.width; texel_size[1] = 1 / currentSource.height; + currentSource.blit( currentDestination, shader.uniforms(uniforms) ); + currentSource = currentDestination; + } + + //average + if(this.isOutputConnected(2)) + { + var average_texture = this._average_texture; + if(!average_texture || average_texture.type != tex.type || average_texture.format != tex.format ) + average_texture = this._average_texture = new GL.Texture( 1, 1, { type: tex.type, format: tex.format, filter: gl.LINEAR }); + texel_size[0] = 1 / currentSource.width; texel_size[1] = 1 / currentSource.height; + uniforms.u_intensity = intensity; + uniforms.u_delta = 1; + currentSource.blit( average_texture, shader.uniforms(uniforms) ); + this.setOutputData( 2, average_texture ); + } + + //upscale and blend + gl.enable( gl.BLEND ); + gl.blendFunc( gl.ONE, gl.ONE ); + uniforms.u_intensity = this.getInputOrProperty("persistence"); + uniforms.u_delta = 0.5; + + for (i -= 2; i >= 0; i--) // i-=2 => -1 to point to last element in array, -1 to go to texture above + { + currentDestination = textures[i]; + textures[i] = null; + texel_size[0] = 1 / currentSource.width; texel_size[1] = 1 / currentSource.height; + currentSource.blit( currentDestination, shader.uniforms(uniforms) ); + GL.Texture.releaseTemporary( currentSource ); + currentSource = currentDestination; + } + gl.disable( gl.BLEND ); + + //glow + if(this.isOutputConnected(1)) + { + var glow_texture = this._glow_texture; + if(!glow_texture || glow_texture.width != tex.width || glow_texture.height != tex.height || glow_texture.type != type || glow_texture.format != tex.format ) + glow_texture = this._glow_texture = new GL.Texture( tex.width, tex.height, { type: type, format: tex.format, filter: gl.LINEAR }); + currentSource.blit( glow_texture ); + this.setOutputData( 1, glow_texture); + } + + //final composition + if(this.isOutputConnected(0)) + { + var final_texture = this._final_texture; + if(!final_texture || final_texture.width != tex.width || final_texture.height != tex.height || final_texture.type != type || final_texture.format != tex.format ) + final_texture = this._final_texture = new GL.Texture( tex.width, tex.height, { type: type, format: tex.format, filter: gl.LINEAR }); + + var dirt_texture = this.getInputData(1); + var dirt_factor = this.getInputOrProperty("dirt_factor"); + + uniforms.u_intensity = intensity; + + shader = dirt_texture ? LGraphTextureGlow._dirt_final_shader : LGraphTextureGlow._final_shader; + if(!shader) + { + if(dirt_texture) + shader = LGraphTextureGlow._dirt_final_shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureGlow.final_pixel_shader, { USE_DIRT: "" } ); + else + shader = LGraphTextureGlow._final_shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphTextureGlow.final_pixel_shader ); + } + + final_texture.drawTo( function(){ + tex.bind(0); + currentSource.bind(1); + if(dirt_texture) + { + shader.setUniform( "u_dirt_factor", dirt_factor ); + shader.setUniform( "u_dirt_texture", dirt_texture.bind(2) ); + } + shader.toViewport( uniforms ); + }); + this.setOutputData( 0, final_texture ); + } + + GL.Texture.releaseTemporary( currentSource ); + } + + LGraphTextureGlow.cut_pixel_shader = "precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + uniform float u_threshold;\n\ + void main() {\n\ + gl_FragColor = max( texture2D( u_texture, v_coord ) - vec4( u_threshold ), vec4(0.0) );\n\ + }" + + LGraphTextureGlow.scale_pixel_shader = "precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + uniform vec2 u_texel_size;\n\ + uniform float u_delta;\n\ + uniform float u_intensity;\n\ + \n\ + vec4 sampleBox(vec2 uv) {\n\ + vec4 o = u_texel_size.xyxy * vec2(-u_delta, u_delta).xxyy;\n\ + vec4 s = texture2D( u_texture, uv + o.xy ) + texture2D( u_texture, uv + o.zy) + texture2D( u_texture, uv + o.xw) + texture2D( u_texture, uv + o.zw);\n\ + return s * 0.25;\n\ + }\n\ + void main() {\n\ + gl_FragColor = u_intensity * sampleBox( v_coord );\n\ + }" + + LGraphTextureGlow.final_pixel_shader = "precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + uniform sampler2D u_glow_texture;\n\ + #ifdef USE_DIRT\n\ + uniform sampler2D u_dirt_texture;\n\ + #endif\n\ + uniform vec2 u_texel_size;\n\ + uniform float u_delta;\n\ + uniform float u_intensity;\n\ + uniform float u_dirt_factor;\n\ + \n\ + vec4 sampleBox(vec2 uv) {\n\ + vec4 o = u_texel_size.xyxy * vec2(-u_delta, u_delta).xxyy;\n\ + vec4 s = texture2D( u_glow_texture, uv + o.xy ) + texture2D( u_glow_texture, uv + o.zy) + texture2D( u_glow_texture, uv + o.xw) + texture2D( u_glow_texture, uv + o.zw);\n\ + return s * 0.25;\n\ + }\n\ + void main() {\n\ + vec4 glow = sampleBox( v_coord );\n\ + #ifdef USE_DIRT\n\ + glow = mix( glow, glow * texture2D( u_dirt_texture, v_coord ), u_dirt_factor );\n\ + #endif\n\ + gl_FragColor = texture2D( u_texture, v_coord ) + u_intensity * glow;\n\ + }" + + LiteGraph.registerNodeType("texture/glow", LGraphTextureGlow ); + + // Texture Blur ***************************************** function LGraphTextureKuwaharaFilter() { @@ -12164,12 +12745,18 @@ LGraphTextureKuwaharaFilter.pixel_shader = "\n\ LGraphTextureWebcam.prototype.onRemoved = function() { - if(this._webcam_stream) + if(!this._webcam_stream) + return; + + var video_streams = this._webcam_stream.getVideoTracks(); + if(video_streams.length) { - this._webcam_stream.stop(); - this._webcam_stream = null; - this._video = null; + var webcam = video_streams[0]; + webcam.stop(); } + + this._webcam_stream = null; + this._video = null; } LGraphTextureWebcam.prototype.onDrawBackground = function(ctx) @@ -12225,15 +12812,432 @@ LGraphTextureKuwaharaFilter.pixel_shader = "\n\ LiteGraph.registerNodeType("texture/webcam", LGraphTextureWebcam ); + + //from https://github.com/spite/Wagner + function LGraphLensFX() + { + this.addInput("in","Texture"); + this.addInput("f","number"); + this.addOutput("out","Texture"); + this.properties = { enabled: true, factor: 1, precision: LGraphTexture.LOW }; + + this._uniforms = { u_texture: 0, u_factor: 1 }; + } + + LGraphLensFX.title = "Lens FX"; + LGraphLensFX.desc = "distortion and chromatic aberration"; + + LGraphLensFX.widgets_info = { + "precision": { widget:"combo", values: LGraphTexture.MODE_VALUES } + }; + + LGraphLensFX.prototype.onGetInputs = function() { return [["enabled","boolean"]]; } + + LGraphLensFX.prototype.onExecute = function() + { + var tex = this.getInputData(0); + if(!tex) + return; + + if(!this.isOutputConnected(0)) + return; //saves work + + if(this.properties.precision === LGraphTexture.PASS_THROUGH || this.getInputOrProperty("enabled" ) === false ) + { + this.setOutputData(0, tex ); + return; + } + + var temp = this._temp_texture; + if(!temp || temp.width != tex.width || temp.height != tex.height || temp.type != tex.type ) + temp = this._temp_texture = new GL.Texture( tex.width, tex.height, { type: tex.type, format: gl.RGBA, filter: gl.LINEAR }); + + var shader = LGraphLensFX._shader; + if(!shader) + shader = LGraphLensFX._shader = new GL.Shader( GL.Shader.SCREEN_VERTEX_SHADER, LGraphLensFX.pixel_shader ); + + var factor = this.getInputData(1); + if(factor == null) + factor = this.properties.factor; + + var uniforms = this._uniforms; + uniforms.u_factor = factor; + + //apply shader + gl.disable( gl.DEPTH_TEST ); + temp.drawTo(function(){ + tex.bind(0); + shader.uniforms(uniforms).draw( GL.Mesh.getScreenQuad() ); + }); + + this.setOutputData(0,temp); + } + + LGraphLensFX.pixel_shader = "precision highp float;\n\ + varying vec2 v_coord;\n\ + uniform sampler2D u_texture;\n\ + uniform float u_factor;\n\ + vec2 barrelDistortion(vec2 coord, float amt) {\n\ + vec2 cc = coord - 0.5;\n\ + float dist = dot(cc, cc);\n\ + return coord + cc * dist * amt;\n\ + }\n\ + \n\ + float sat( float t )\n\ + {\n\ + return clamp( t, 0.0, 1.0 );\n\ + }\n\ + \n\ + float linterp( float t ) {\n\ + return sat( 1.0 - abs( 2.0*t - 1.0 ) );\n\ + }\n\ + \n\ + float remap( float t, float a, float b ) {\n\ + return sat( (t - a) / (b - a) );\n\ + }\n\ + \n\ + vec4 spectrum_offset( float t ) {\n\ + vec4 ret;\n\ + float lo = step(t,0.5);\n\ + float hi = 1.0-lo;\n\ + float w = linterp( remap( t, 1.0/6.0, 5.0/6.0 ) );\n\ + ret = vec4(lo,1.0,hi, 1.) * vec4(1.0-w, w, 1.0-w, 1.);\n\ + \n\ + return pow( ret, vec4(1.0/2.2) );\n\ + }\n\ + \n\ + const float max_distort = 2.2;\n\ + const int num_iter = 12;\n\ + const float reci_num_iter_f = 1.0 / float(num_iter);\n\ + \n\ + void main()\n\ + { \n\ + vec2 uv=v_coord;\n\ + vec4 sumcol = vec4(0.0);\n\ + vec4 sumw = vec4(0.0); \n\ + for ( int i=0; ia&&nb?!0:!1}function s(a,b){var c=a[0]+a[2],n=a[1]+a[3],h=b[1]+b[3];return a[0]>b[0]+b[2]||a[1]>h||cv.width- -e.width-10&&(d=v.width-e.width-10);k>v.height-e.height-10&&(k=v.height-e.height-10)}h.style.left=d+"px";h.style.top=k+"px"}var l=r.LiteGraph={NODE_TITLE_HEIGHT:16,NODE_SLOT_HEIGHT:15,NODE_WIDTH:140,NODE_MIN_WIDTH:50,NODE_COLLAPSED_RADIUS:10,NODE_COLLAPSED_WIDTH:80,CANVAS_GRID_SIZE:10,NODE_TITLE_COLOR:"#222",NODE_DEFAULT_COLOR:"#999",NODE_DEFAULT_BGCOLOR:"#444",NODE_DEFAULT_BOXCOLOR:"#AEF",NODE_DEFAULT_SHAPE:"box",MAX_NUMBER_OF_NODES:1E3,DEFAULT_POSITION:[100,100],node_images_path:"",VALID_SHAPES:["box", +a&&a.constructor===String&&(a=document.querySelector(a));this.max_zoom=10;this.min_zoom=0.1;this.zoom_modify_alpha=!1;this.title_text_font="bold 14px Arial";this.inner_text_font="normal 12px Arial";this.node_title_color=l.NODE_TITLE_COLOR;this.default_link_color="#AAC";this.default_connection_color={input_off:"#AAB",input_on:"#7F7",output_off:"#AAB",output_on:"#7F7"};this.highquality_render=!0;this.editor_alpha=1;this.pause_rendering=!1;this.render_only_selected=this.clear_background=this.render_shadows= +!0;this.live_mode=!1;this.allow_interaction=this.allow_dragnodes=this.allow_dragcanvas=this.show_info=!0;this.drag_mode=!1;this.dragging_rectangle=null;this.always_render_background=!1;this.render_canvas_border=!0;this.render_connections_shadows=!1;this.render_connection_arrows=this.render_curved_connections=this.render_connections_border=!0;this.connections_width=3;b&&b.attachCanvas(this);this.setCanvas(a);this.clear();c.skip_render||this.startRendering();this.autoresize=c.autoresize}function p(a, +b){return Math.sqrt((b[0]-a[0])*(b[0]-a[0])+(b[1]-a[1])*(b[1]-a[1]))}function q(a,b,c,n,h,d){return ca&&nb?!0:!1}function s(a,b){var c=a[0]+a[2],n=a[1]+a[3],h=b[1]+b[3];return a[0]>b[0]+b[2]||a[1]>h||cv.width-e.width-10&&(d=v.width-e.width-10);k>v.height-e.height-10&&(k=v.height-e.height-10)}h.style.left=d+"px";h.style.top=k+"px"}var l=r.LiteGraph={NODE_TITLE_HEIGHT:16,NODE_SLOT_HEIGHT:15,NODE_WIDTH:140,NODE_MIN_WIDTH:50,NODE_COLLAPSED_RADIUS:10,NODE_COLLAPSED_WIDTH:80,CANVAS_GRID_SIZE:10,NODE_TITLE_COLOR:"#222",NODE_DEFAULT_COLOR:"#999",NODE_DEFAULT_BGCOLOR:"#444",NODE_DEFAULT_BOXCOLOR:"#AEF",NODE_DEFAULT_SHAPE:"box",MAX_NUMBER_OF_NODES:1E3,DEFAULT_POSITION:[100,100],node_images_path:"",VALID_SHAPES:["box", "round"],BOX_SHAPE:1,ROUND_SHAPE:2,CIRCLE_SHAPE:3,INPUT:1,OUTPUT:2,EVENT:-1,ACTION:-1,ALWAYS:0,ON_EVENT:1,NEVER:2,ON_TRIGGER:3,proxy:null,debug:!1,throw_errors:!0,allow_scripts:!0,registered_node_types:{},node_types_by_file_extension:{},Nodes:{},registerNodeType:function(a,b){if(!b.prototype)throw"Cannot register a simple object, it must be a class with a prototype";b.type=a;l.debug&&console.log("Node registered: "+a);a.split("/");var c=b.name,n=a.lastIndexOf("/");b.category=a.substr(0,n);b.title|| (b.title=c);if(b.prototype)for(var h in f.prototype)b.prototype[h]||(b.prototype[h]=f.prototype[h]);Object.defineProperty(b.prototype,"shape",{set:function(a){switch(a){case "box":this._shape=l.BOX_SHAPE;break;case "round":this._shape=l.ROUND_SHAPE;break;case "circle":this._shape=l.CIRCLE_SHAPE;break;default:this._shape=a}},get:function(a){return this._shape},enumerable:!0});this.registered_node_types[a]=b;b.constructor.name&&(this.Nodes[c]=b);b.prototype.onPropertyChange&&console.warn("LiteGraph node class "+ a+" has onPropertyChange method, it must be called onPropertyChanged with d at the end");if(b.supported_extensions)for(h in b.supported_extensions)this.node_types_by_file_extension[b.supported_extensions[h].toLowerCase()]=b},wrapFunctionAsNode:function(a,b,c,n){for(var h=Array(b.length),d="",k=l.getParameterNames(b),v=0;vb-k._last_time&&(f=2-0.002*(b-k._last_time),m="rgba(255,255,255, "+f.toFixed(2)+")",this.renderLink(a,g,h.getConnectionPos(!0,e),k,!0,f,m))}}}}a.globalAlpha=1}; -e.prototype.renderLink=function(a,b,c,d,h,f,k){if(this.highquality_render){var m=p(b,c);this.render_connections_border&&0.6b[1]?0:Math.PI,a.save(),a.translate(d[0], -d[1]),a.rotate(k),a.beginPath(),a.moveTo(-5,-5),a.lineTo(0,5),a.lineTo(5,-5),a.fill(),a.restore());if(f)for(f=0;5>f;++f)d=(0.001*l.getTime()+0.2*f)%1,d=this.computeConnectionPoint(b,c,d),a.beginPath(),a.arc(d[0],d[1],5,0,2*Math.PI),a.fill()}else a.beginPath(),a.moveTo(b[0],b[1]),a.lineTo(c[0],c[1]),a.stroke()};e.prototype.computeConnectionPoint=function(a,b,c){var d=p(a,b),h=[a[0]+0.25*d,a[1]],d=[b[0]-0.25*d,b[1]],e=(1-c)*(1-c)*(1-c),k=3*(1-c)*(1-c)*c,f=3*(1-c)*c*c;c*=c*c;return[e*a[0]+k*h[0]+f*d[0]+ -c*b[0],e*a[1]+k*h[1]+f*d[1]+c*b[1]]};e.prototype.resize=function(a,b){if(!a&&!b){var c=this.canvas.parentNode;a=c.offsetWidth;b=c.offsetHeight}if(this.canvas.width!=a||this.canvas.height!=b)this.canvas.width=a,this.canvas.height=b,this.bgcanvas.width=this.canvas.width,this.bgcanvas.height=this.canvas.height,this.setDirty(!0,!0)};e.prototype.switchLiveMode=function(a){if(a){var b=this,c=this.live_mode?1.1:0.9;this.live_mode&&(this.live_mode=!1,this.editor_alpha=0.1);var d=setInterval(function(){b.editor_alpha*= -c;b.dirty_canvas=!0;b.dirty_bgcanvas=!0;1>c&&0.01>b.editor_alpha&&(clearInterval(d),1>c&&(b.live_mode=!0));1"+g+""+a+"",value:g});if(m.length)return new l.ContextMenu(m,{event:c,callback:f,parentMenu:d,allow_html:!0,node:h},b),!1}};e.decodeHTML=function(a){var b=document.createElement("div");b.innerText=a;return b.innerHTML};e.onResizeNode=function(a,b,c,d,h){h&&(h.size=h.computeSize(),h.setDirtyCanvas(!0,!0))};e.onShowTitleEditor= -function(a,b,c,d,h){function f(){h.title=m.value;k.parentNode.removeChild(k);h.setDirtyCanvas(!0,!0)}var k=document.createElement("div");k.className="graphdialog";k.innerHTML="Title";var m=k.querySelector("input");m&&(m.value=h.title,m.addEventListener("keydown",function(a){13==a.keyCode&&(f(),a.preventDefault(),a.stopPropagation())}));a=e.active_canvas.canvas;b=a.getBoundingClientRect();d=c=-20;b&&(c-=b.left, -d-=b.top);event?(k.style.left=event.pageX+c+"px",k.style.top=event.pageY+d+"px"):(k.style.left=0.5*a.width+c+"px",k.style.top=0.5*a.height+d+"px");k.querySelector("button").addEventListener("click",f);a.parentNode.appendChild(k)};e.prototype.showEditPropertyValue=function(a,b,c){function d(){h(p.value)}function h(c){"number"==typeof a.properties[b]&&(c=Number(c));a.properties[b]=c;if(a.onPropertyChanged)a.onPropertyChanged(b,c);l.close();a.setDirtyCanvas(!0,!0)}if(a&&void 0!==a.properties[b]){c=c|| -{};var e="string";null!==a.properties[b]&&(e=typeof a.properties[b]);var k=null;a.getPropertyInfo&&(k=a.getPropertyInfo(b));if(a.properties_info)for(var f=0;f";else if("enum"==e&&k.values){m=""}else"boolean"==e&&(m="");var l=this.createDialog(""+b+""+m+"",c);if("enum"==e&&k.values){var p=l.querySelector("select");p.addEventListener("change",function(a){h(a.target.value)})}else if("boolean"==e)(p=l.querySelector("input"))&&p.addEventListener("click", -function(a){h(!!p.checked)});else if(p=l.querySelector("input"))p.value=void 0!==a.properties[b]?a.properties[b]:"",p.addEventListener("keydown",function(a){13==a.keyCode&&(d(),a.preventDefault(),a.stopPropagation())});l.querySelector("button").addEventListener("click",d)}};e.prototype.createDialog=function(a,b){b=b||{};var c=document.createElement("div");c.className="graphdialog";c.innerHTML=a;var d=this.canvas.getBoundingClientRect(),h=-20,e=-20;d&&(h-=d.left,e-=d.top);b.position?(h+=b.position[0], -e+=b.position[1]):b.event?(h+=b.event.pageX,e+=b.event.pageY):(h+=0.5*this.canvas.width,e+=0.5*this.canvas.height);c.style.left=h+"px";c.style.top=e+"px";this.canvas.parentNode.appendChild(c);c.close=function(){this.parentNode&&this.parentNode.removeChild(this)};return c};e.onMenuNodeCollapse=function(a,b,c,d,e){e.flags.collapsed=!e.flags.collapsed;e.setDirtyCanvas(!0,!0)};e.onMenuNodePin=function(a,b,c,d,e){e.pin()};e.onMenuNodeMode=function(a,b,c,d,e){new l.ContextMenu(["Always","On Event","On Trigger", -"Never"],{event:c,callback:function(a){if(e)switch(a){case "On Event":e.mode=l.ON_EVENT;break;case "On Trigger":e.mode=l.ON_TRIGGER;break;case "Never":e.mode=l.NEVER;break;default:e.mode=l.ALWAYS}},parentMenu:d,node:e});return!1};e.onMenuNodeColors=function(a,b,c,d,h){if(!h)throw"no node for color";b=[];for(var f in e.node_colors)a=e.node_colors[f],a={value:f,content:""+f+""},b.push(a);new l.ContextMenu(b,{event:c, -callback:function(a){h&&(a=e.node_colors[a.value])&&(h.color=a.color,h.bgcolor=a.bgcolor,h.setDirtyCanvas(!0))},parentMenu:d,node:h});return!1};e.onMenuNodeShapes=function(a,b,c,d,e){if(!e)throw"no node passed";new l.ContextMenu(l.VALID_SHAPES,{event:c,callback:function(a){e&&(e.shape=a,e.setDirtyCanvas(!0))},parentMenu:d,node:e});return!1};e.onMenuNodeRemove=function(a,b,c,d,e){if(!e)throw"no node passed";!1!=e.removable&&(e.graph.remove(e),e.setDirtyCanvas(!0,!0))};e.onMenuNodeClone=function(a, -b,c,d,e){!1!=e.clonable&&(a=e.clone())&&(a.pos=[e.pos[0]+5,e.pos[1]+5],e.graph.add(a),e.setDirtyCanvas(!0,!0))};e.node_colors={red:{color:"#FAA",bgcolor:"#944"},green:{color:"#AFA",bgcolor:"#494"},blue:{color:"#AAF",bgcolor:"#449"},cyan:{color:"#AFF",bgcolor:"#499"},purple:{color:"#FAF",bgcolor:"#949"},yellow:{color:"#FFA",bgcolor:"#994"},black:{color:"#777",bgcolor:"#000"},white:{color:"#FFF",bgcolor:"#AAA"}};e.prototype.getCanvasMenuOptions=function(){var a=null;this.getMenuOptions?a=this.getMenuOptions(): -(a=[{content:"Add Node",has_submenu:!0,callback:e.onMenuAdd}],this._graph_stack&&0Name",d),h=f.querySelector("input");f.querySelector("button").addEventListener("click",function(b){if(h.value){if(b=k.input?a.getInputInfo(k.slot):a.getOutputInfo(k.slot))b.label= -h.value;c.setDirty(!0)}f.close()})}},node:a},k=null;a&&(k=a.getSlotInPosition(b.canvasX,b.canvasY),e.active_node=a);k?(f=[],f.push(k.locked?"Cannot remove":{content:"Remove Slot",slot:k}),f.push({content:"Rename Slot",slot:k}),m.title=(k.input?k.input.type:k.output.type)||"*",k.input&&k.input.type==l.ACTION&&(m.title="Action"),k.output&&k.output.type==l.EVENT&&(m.title="Event")):f=a?this.getNodeMenuOptions(a):this.getCanvasMenuOptions();f&&new l.ContextMenu(f,m,d)};this.CanvasRenderingContext2D&& -(CanvasRenderingContext2D.prototype.roundRect=function(a,b,c,d,e,f){void 0===e&&(e=5);void 0===f&&(f=e);this.beginPath();this.moveTo(a+e,b);this.lineTo(a+c-e,b);this.quadraticCurveTo(a+c,b,a+c,b+e);this.lineTo(a+c,b+d-f);this.quadraticCurveTo(a+c,b+d,a+c-f,b+d);this.lineTo(a+f,b+d);this.quadraticCurveTo(a,b+d,a,b+d-f);this.lineTo(a,b+e);this.quadraticCurveTo(a,b,a+e,b)});l.compareObjects=function(a,b){for(var c in a)if(a[c]!=b[c])return!1;return!0};l.distance=p;l.colorToString=function(a){return"rgba("+ -Math.round(255*a[0]).toFixed()+","+Math.round(255*a[1]).toFixed()+","+Math.round(255*a[2]).toFixed()+","+(4==a.length?a[3].toFixed(2):"1.0")+")"};l.isInsideRectangle=q;l.growBounding=function(a,b,c){ba[2]&&(a[2]=b);ca[3]&&(a[3]=c)};l.isInsideBounding=function(a,b){return a[0]b[1][0]||a[1]>b[1][1]?!1:!0};l.overlapBounding=s;l.hex2num=function(a){"#"==a.charAt(0)&&(a=a.slice(1));a=a.toUpperCase();for(var b=Array(3),c=0,d,e,f=0;6>f;f+=2)d="0123456789ABCDEF".indexOf(a.charAt(f)), -e="0123456789ABCDEF".indexOf(a.charAt(f+1)),b[c]=16*d+e,c++;return b};l.num2hex=function(a){for(var b="#",c,d,e=0;3>e;e++)c=a[e]/16,d=a[e]%16,b+="0123456789ABCDEF".charAt(c)+"0123456789ABCDEF".charAt(d);return b};u.prototype.addItem=function(a,b,c){function d(a){var b=this.value;b&&b.has_submenu&&e.call(this,a)}function e(a){var b=this.value,d=!0;f.current_submenu&&f.current_submenu.close(a);if(c.callback){var k=c.callback.call(this,b,c,a,f,c.node);!0===k&&(d=!1)}if(b&&(b.callback&&!c.ignore_item_callbacks&& -!0!==b.disabled&&(k=b.callback.call(this,b,c,a,f,c.node),!0===k&&(d=!1)),b.submenu)){if(!b.submenu.options)throw"ContextMenu submenu needs options";new f.constructor(b.submenu.options,{callback:b.submenu.callback,event:a,parentMenu:f,ignore_item_callbacks:b.submenu.ignore_item_callbacks,title:b.submenu.title,autoopen:c.autoopen});d=!1}d&&!f.lock&&f.close()}var f=this;c=c||{};var k=document.createElement("div");k.className="litemenu-entry submenu";var m=!1;if(null===b)k.classList.add("separator"); -else{k.innerHTML=b&&b.title?b.title:a;if(k.value=b)b.disabled&&(m=!0,k.classList.add("disabled")),(b.submenu||b.has_submenu)&&k.classList.add("has_submenu");"function"==typeof b?(k.dataset.value=a,k.onclick_callback=b):k.dataset.value=b;b.className&&(k.className+=" "+b.className)}this.root.appendChild(k);m||k.addEventListener("click",e);c.autoopen&&k.addEventListener("mouseenter",d);return k};u.prototype.close=function(a,b){this.root.parentNode&&this.root.parentNode.removeChild(this.root);this.parentMenu&& -!b&&(this.parentMenu.lock=!1,this.parentMenu.current_submenu=null,void 0===a?this.parentMenu.close():a&&!u.isCursorOverElement(a,this.parentMenu.root)&&u.trigger(this.parentMenu.root,"mouseleave",a));this.current_submenu&&this.current_submenu.close(a,!0)};u.trigger=function(a,b,c,d){var e=document.createEvent("CustomEvent");e.initCustomEvent(b,!0,!0,c);e.srcElement=d;a.dispatchEvent?a.dispatchEvent(e):a.__events&&a.__events.dispatchEvent(e);return e};u.prototype.getTopMenu=function(){return this.options.parentMenu? -this.options.parentMenu.getTopMenu():this};u.prototype.getFirstEvent=function(){return this.options.parentMenu?this.options.parentMenu.getFirstEvent():this.options.event};u.isCursorOverElement=function(a,b){var c=a.pageX,d=a.pageY,e=b.getBoundingClientRect();return e?d>e.top&&de.left&&ca?b:cb-k._last_time&&(f=2-0.002*(b-k._last_time),m="rgba(255,255,255, "+f.toFixed(2)+")",this.renderLink(a,g,h.getConnectionPos(!0, +e),k,!0,f,m))}}}}a.globalAlpha=1};e.prototype.renderLink=function(a,b,c,d,h,f,k){if(this.highquality_render){var m=p(b,c);this.render_connections_border&&0.6 +b[1]?0:Math.PI,a.save(),a.translate(d[0],d[1]),a.rotate(k),a.beginPath(),a.moveTo(-5,-5),a.lineTo(0,5),a.lineTo(5,-5),a.fill(),a.restore());if(f)for(f=0;5>f;++f)d=(0.001*l.getTime()+0.2*f)%1,d=this.computeConnectionPoint(b,c,d),a.beginPath(),a.arc(d[0],d[1],5,0,2*Math.PI),a.fill()}else a.beginPath(),a.moveTo(b[0],b[1]),a.lineTo(c[0],c[1]),a.stroke()};e.prototype.computeConnectionPoint=function(a,b,c){var d=p(a,b),h=[a[0]+0.25*d,a[1]],d=[b[0]-0.25*d,b[1]],e=(1-c)*(1-c)*(1-c),k=3*(1-c)*(1-c)*c,f=3* +(1-c)*c*c;c*=c*c;return[e*a[0]+k*h[0]+f*d[0]+c*b[0],e*a[1]+k*h[1]+f*d[1]+c*b[1]]};e.prototype.resize=function(a,b){if(!a&&!b){var c=this.canvas.parentNode;a=c.offsetWidth;b=c.offsetHeight}if(this.canvas.width!=a||this.canvas.height!=b)this.canvas.width=a,this.canvas.height=b,this.bgcanvas.width=this.canvas.width,this.bgcanvas.height=this.canvas.height,this.setDirty(!0,!0)};e.prototype.switchLiveMode=function(a){if(a){var b=this,c=this.live_mode?1.1:0.9;this.live_mode&&(this.live_mode=!1,this.editor_alpha= +0.1);var d=setInterval(function(){b.editor_alpha*=c;b.dirty_canvas=!0;b.dirty_bgcanvas=!0;1>c&&0.01>b.editor_alpha&&(clearInterval(d),1>c&&(b.live_mode=!0));1"+g+""+a+"",value:g});if(m.length)return new l.ContextMenu(m,{event:c,callback:f,parentMenu:d,allow_html:!0,node:h},b),!1}};e.decodeHTML=function(a){var b=document.createElement("div");b.innerText=a;return b.innerHTML};e.onResizeNode=function(a, +b,c,d,h){h&&(h.size=h.computeSize(),h.setDirtyCanvas(!0,!0))};e.onShowTitleEditor=function(a,b,c,d,h){function f(){h.title=m.value;k.parentNode.removeChild(k);h.setDirtyCanvas(!0,!0)}var k=document.createElement("div");k.className="graphdialog";k.innerHTML="Title";var m=k.querySelector("input");m&&(m.value=h.title,m.addEventListener("keydown",function(a){13==a.keyCode&&(f(),a.preventDefault(),a.stopPropagation())})); +a=e.active_canvas.canvas;b=a.getBoundingClientRect();d=c=-20;b&&(c-=b.left,d-=b.top);event?(k.style.left=event.pageX+c+"px",k.style.top=event.pageY+d+"px"):(k.style.left=0.5*a.width+c+"px",k.style.top=0.5*a.height+d+"px");k.querySelector("button").addEventListener("click",f);a.parentNode.appendChild(k)};e.prototype.showEditPropertyValue=function(a,b,c){function d(){h(p.value)}function h(c){"number"==typeof a.properties[b]&&(c=Number(c));a.properties[b]=c;if(a.onPropertyChanged)a.onPropertyChanged(b, +c);l.close();a.setDirtyCanvas(!0,!0)}if(a&&void 0!==a.properties[b]){c=c||{};var e="string";null!==a.properties[b]&&(e=typeof a.properties[b]);var k=null;a.getPropertyInfo&&(k=a.getPropertyInfo(b));if(a.properties_info)for(var f=0;f";else if("enum"==e&&k.values){m=""}else"boolean"==e&&(m="");var l=this.createDialog(""+b+""+m+"",c);if("enum"==e&&k.values){var p=l.querySelector("select");p.addEventListener("change",function(a){h(a.target.value)})}else if("boolean"== +e)(p=l.querySelector("input"))&&p.addEventListener("click",function(a){h(!!p.checked)});else if(p=l.querySelector("input"))p.value=void 0!==a.properties[b]?a.properties[b]:"",p.addEventListener("keydown",function(a){13==a.keyCode&&(d(),a.preventDefault(),a.stopPropagation())});l.querySelector("button").addEventListener("click",d)}};e.prototype.createDialog=function(a,b){b=b||{};var c=document.createElement("div");c.className="graphdialog";c.innerHTML=a;var d=this.canvas.getBoundingClientRect(),h= +-20,e=-20;d&&(h-=d.left,e-=d.top);b.position?(h+=b.position[0],e+=b.position[1]):b.event?(h+=b.event.pageX,e+=b.event.pageY):(h+=0.5*this.canvas.width,e+=0.5*this.canvas.height);c.style.left=h+"px";c.style.top=e+"px";this.canvas.parentNode.appendChild(c);c.close=function(){this.parentNode&&this.parentNode.removeChild(this)};return c};e.onMenuNodeCollapse=function(a,b,c,d,e){e.flags.collapsed=!e.flags.collapsed;e.setDirtyCanvas(!0,!0)};e.onMenuNodePin=function(a,b,c,d,e){e.pin()};e.onMenuNodeMode= +function(a,b,c,d,e){new l.ContextMenu(["Always","On Event","On Trigger","Never"],{event:c,callback:function(a){if(e)switch(a){case "On Event":e.mode=l.ON_EVENT;break;case "On Trigger":e.mode=l.ON_TRIGGER;break;case "Never":e.mode=l.NEVER;break;default:e.mode=l.ALWAYS}},parentMenu:d,node:e});return!1};e.onMenuNodeColors=function(a,b,c,d,h){if(!h)throw"no node for color";b=[];for(var f in e.node_colors)a=e.node_colors[f],a={value:f,content:""+f+""},b.push(a);new l.ContextMenu(b,{event:c,callback:function(a){h&&(a=e.node_colors[a.value])&&(h.color=a.color,h.bgcolor=a.bgcolor,h.setDirtyCanvas(!0))},parentMenu:d,node:h});return!1};e.onMenuNodeShapes=function(a,b,c,d,e){if(!e)throw"no node passed";new l.ContextMenu(l.VALID_SHAPES,{event:c,callback:function(a){e&&(e.shape=a,e.setDirtyCanvas(!0))},parentMenu:d,node:e});return!1};e.onMenuNodeRemove=function(a,b,c,d,e){if(!e)throw"no node passed";!1!=e.removable&&(e.graph.remove(e), +e.setDirtyCanvas(!0,!0))};e.onMenuNodeClone=function(a,b,c,d,e){!1!=e.clonable&&(a=e.clone())&&(a.pos=[e.pos[0]+5,e.pos[1]+5],e.graph.add(a),e.setDirtyCanvas(!0,!0))};e.node_colors={red:{color:"#FAA",bgcolor:"#944"},green:{color:"#AFA",bgcolor:"#494"},blue:{color:"#AAF",bgcolor:"#449"},cyan:{color:"#AFF",bgcolor:"#499"},purple:{color:"#FAF",bgcolor:"#949"},yellow:{color:"#FFA",bgcolor:"#994"},black:{color:"#777",bgcolor:"#000"},white:{color:"#FFF",bgcolor:"#AAA"}};e.prototype.getCanvasMenuOptions= +function(){var a=null;this.getMenuOptions?a=this.getMenuOptions():(a=[{content:"Add Node",has_submenu:!0,callback:e.onMenuAdd}],this._graph_stack&&0Name",d),h=f.querySelector("input");f.querySelector("button").addEventListener("click",function(b){if(h.value){if(b= +k.input?a.getInputInfo(k.slot):a.getOutputInfo(k.slot))b.label=h.value;c.setDirty(!0)}f.close()})}},node:a},k=null;a&&(k=a.getSlotInPosition(b.canvasX,b.canvasY),e.active_node=a);k?(f=[],f.push(k.locked?"Cannot remove":{content:"Remove Slot",slot:k}),f.push({content:"Rename Slot",slot:k}),m.title=(k.input?k.input.type:k.output.type)||"*",k.input&&k.input.type==l.ACTION&&(m.title="Action"),k.output&&k.output.type==l.EVENT&&(m.title="Event")):f=a?this.getNodeMenuOptions(a):this.getCanvasMenuOptions(); +f&&new l.ContextMenu(f,m,d)};this.CanvasRenderingContext2D&&(CanvasRenderingContext2D.prototype.roundRect=function(a,b,c,d,e,f){void 0===e&&(e=5);void 0===f&&(f=e);this.beginPath();this.moveTo(a+e,b);this.lineTo(a+c-e,b);this.quadraticCurveTo(a+c,b,a+c,b+e);this.lineTo(a+c,b+d-f);this.quadraticCurveTo(a+c,b+d,a+c-f,b+d);this.lineTo(a+f,b+d);this.quadraticCurveTo(a,b+d,a,b+d-f);this.lineTo(a,b+e);this.quadraticCurveTo(a,b,a+e,b)});l.compareObjects=function(a,b){for(var c in a)if(a[c]!=b[c])return!1; +return!0};l.distance=p;l.colorToString=function(a){return"rgba("+Math.round(255*a[0]).toFixed()+","+Math.round(255*a[1]).toFixed()+","+Math.round(255*a[2]).toFixed()+","+(4==a.length?a[3].toFixed(2):"1.0")+")"};l.isInsideRectangle=q;l.growBounding=function(a,b,c){ba[2]&&(a[2]=b);ca[3]&&(a[3]=c)};l.isInsideBounding=function(a,b){return a[0]b[1][0]||a[1]>b[1][1]?!1:!0};l.overlapBounding=s;l.hex2num=function(a){"#"==a.charAt(0)&&(a=a.slice(1)); +a=a.toUpperCase();for(var b=Array(3),c=0,d,e,f=0;6>f;f+=2)d="0123456789ABCDEF".indexOf(a.charAt(f)),e="0123456789ABCDEF".indexOf(a.charAt(f+1)),b[c]=16*d+e,c++;return b};l.num2hex=function(a){for(var b="#",c,d,e=0;3>e;e++)c=a[e]/16,d=a[e]%16,b+="0123456789ABCDEF".charAt(c)+"0123456789ABCDEF".charAt(d);return b};u.prototype.addItem=function(a,b,c){function d(a){var b=this.value;b&&b.has_submenu&&e.call(this,a)}function e(a){var b=this.value,d=!0;f.current_submenu&&f.current_submenu.close(a);if(c.callback){var k= +c.callback.call(this,b,c,a,f,c.node);!0===k&&(d=!1)}if(b&&(b.callback&&!c.ignore_item_callbacks&&!0!==b.disabled&&(k=b.callback.call(this,b,c,a,f,c.node),!0===k&&(d=!1)),b.submenu)){if(!b.submenu.options)throw"ContextMenu submenu needs options";new f.constructor(b.submenu.options,{callback:b.submenu.callback,event:a,parentMenu:f,ignore_item_callbacks:b.submenu.ignore_item_callbacks,title:b.submenu.title,autoopen:c.autoopen});d=!1}d&&!f.lock&&f.close()}var f=this;c=c||{};var k=document.createElement("div"); +k.className="litemenu-entry submenu";var m=!1;if(null===b)k.classList.add("separator");else{k.innerHTML=b&&b.title?b.title:a;if(k.value=b)b.disabled&&(m=!0,k.classList.add("disabled")),(b.submenu||b.has_submenu)&&k.classList.add("has_submenu");"function"==typeof b?(k.dataset.value=a,k.onclick_callback=b):k.dataset.value=b;b.className&&(k.className+=" "+b.className)}this.root.appendChild(k);m||k.addEventListener("click",e);c.autoopen&&k.addEventListener("mouseenter",d);return k};u.prototype.close= +function(a,b){this.root.parentNode&&this.root.parentNode.removeChild(this.root);this.parentMenu&&!b&&(this.parentMenu.lock=!1,this.parentMenu.current_submenu=null,void 0===a?this.parentMenu.close():a&&!u.isCursorOverElement(a,this.parentMenu.root)&&u.trigger(this.parentMenu.root,"mouseleave",a));this.current_submenu&&this.current_submenu.close(a,!0)};u.trigger=function(a,b,c,d){var e=document.createEvent("CustomEvent");e.initCustomEvent(b,!0,!0,c);e.srcElement=d;a.dispatchEvent?a.dispatchEvent(e): +a.__events&&a.__events.dispatchEvent(e);return e};u.prototype.getTopMenu=function(){return this.options.parentMenu?this.options.parentMenu.getTopMenu():this};u.prototype.getFirstEvent=function(){return this.options.parentMenu?this.options.parentMenu.getFirstEvent():this.options.event};u.isCursorOverElement=function(a,b){var c=a.pageX,d=a.pageY,e=b.getBoundingClientRect();return e?d>e.top&&de.left&&ca?b:c